<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>mgmarlow.com - List of blog posts</title>
    <subtitle>Graham Marlow on the internet.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://mgmarlow.com/words/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-05-30T00:00:00+00:00</updated>
    <id>https://mgmarlow.com/words/atom.xml</id>
    <entry xml:lang="en">
        <title>Revisiting My Reading List</title>
        <published>2026-05-30T00:00:00+00:00</published>
        <updated>2026-05-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2026-05-30-revisiting-my-reading-list/"/>
        <id>https://mgmarlow.com/words/2026-05-30-revisiting-my-reading-list/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2026-05-30-revisiting-my-reading-list/">&lt;p&gt;The best kind of weekend project takes just a few scattered hours
while solving a supremely pedantic annoyance. That&#x27;s the story of my
hand-rolled book-tracking replacement.&lt;&#x2F;p&gt;
&lt;p&gt;The idea stemmed from a discussion with friends, where a conversation
about books transitioned into how we keep track of our respective lists.&lt;&#x2F;p&gt;
&lt;p&gt;Unlike Letterboxd, which solves movie-tracking with a sublime grace,
every reading platform kind of stinks. Goodreads is a vehicle for
advertisement that has languished under Amazon&#x27;s stewardship. Storygraph
is the Next Best Thing, but is (unfortunately) supremely ugly. Margins
looks neat but caters too heavily to the obsessive-impulsiveness of the
BookTok crowd. Hardcover gives me whiplash every time it swaps between
open source fantasy and legitimate product.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t mean to be too hard on these products, making a social book site
is a surprisingly hard endeavor. The world of editions, publishers, and
ISBNs is an absolute mess! But look, I just need a place to keep a list
of books that I can share with my friends. I don&#x27;t need recommendations,
I don&#x27;t need reviews. I don&#x27;t use these sites to find my next read.&lt;&#x2F;p&gt;
&lt;p&gt;And so, taking a page out of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;macwright.com&#x2F;reading&#x2F;&quot;&gt;Tom MacWright&#x27;s
book&lt;&#x2F;a&gt;, I absorbed my reading list into
&lt;a href=&quot;&#x2F;reading&quot;&gt;this very site&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I keep my list as a YAML file that is easy enough to keep updated as I
rotate through books:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt; title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;A Wild Sheep Chase&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  author&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Haruki Murakami&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;read&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  started&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; 2026-04-22&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  finished&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; 2026-05-05&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  isbn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 9780375718946&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I compile this YAML file into two different file types. The first is the
HTML template that becomes &lt;a href=&quot;&#x2F;reading&quot;&gt;&#x2F;reading&lt;&#x2F;a&gt;. The second is an Atom
feed that I use to push updates to a Discord channel.&lt;&#x2F;p&gt;
&lt;p&gt;That second point is inspired by
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jimlind&#x2F;filmlinkd&quot;&gt;Filmlinkd&lt;&#x2F;a&gt;, which our Discord
server uses to post Letterboxd activity into a text channel. Since my
books are tracked in a YAML file, I need some mechanism for tracking
changes and triggering a message. That mechanism is an Atom feed and a
Cloudflare Worker.&lt;&#x2F;p&gt;
&lt;p&gt;Atom is a great fit for this job. Each book entry has a natural
ID formed by it&#x27;s ISBN, which I form into an OpenLibrary URL:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;openlibrary.org&#x2F;isbn&#x2F;9780375718946&quot;&gt;https:&#x2F;&#x2F;openlibrary.org&#x2F;isbn&#x2F;9780375718946&lt;&#x2F;a&gt;. An entry&#x27;s updated-at
field is either the &lt;code&gt;started&lt;&#x2F;code&gt; date or the &lt;code&gt;finished&lt;&#x2F;code&gt; date. A book
transitioning from &quot;reading&quot; to &quot;read&quot; will naturally have a later
&lt;code&gt;finished&lt;&#x2F;code&gt; timestamp, indicating an update.&lt;&#x2F;p&gt;
&lt;p&gt;The update ends up in the Discord server via a Cloudflare Worker. The
worker reads the Atom feed on a periodic basis (every 30min) and looks
for updated items (by comparing the new read to the cache in Cloudflare
KV). If it finds an update, it forms a message and posts it to the
server via a webhook.&lt;&#x2F;p&gt;
&lt;p&gt;Weekend accomplished.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the source: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.sr.ht&#x2F;~mgmarlow&#x2F;reading-list-poster&quot;&gt;https:&#x2F;&#x2F;git.sr.ht&#x2F;~mgmarlow&#x2F;reading-list-poster&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>PGP and Real-World Cryptography</title>
        <published>2026-03-14T00:00:00+00:00</published>
        <updated>2026-03-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2026-03-14-how-good-is-pretty-good-privacy/"/>
        <id>https://mgmarlow.com/words/2026-03-14-how-good-is-pretty-good-privacy/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2026-03-14-how-good-is-pretty-good-privacy/">&lt;p&gt;My previous post details setting up Sequoia PGP for email encryption, where I
gloss over technical details in favor of waxing poetic about the virtues of
privacy. That&#x27;s all well and good, but I don&#x27;t want to leave such details
completely behind because they form an interesting window into the world of
asymmetric cryptography.&lt;&#x2F;p&gt;
&lt;p&gt;This whole PGP excursion has ignited within me a desire to learn more about
cryptography &lt;em&gt;generally&lt;&#x2F;em&gt;, leading to the book
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.manning.com&#x2F;books&#x2F;real-world-cryptography&quot;&gt;&lt;em&gt;Real-World Cryptography&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;.
The author has the following to say about PGP:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The most critical issue is that encryption is not authenticated, which means
that anyone intercepting an email that hasn&#x27;t been signed might be able to
tamper with the encrypted content to some degree, depending on the exact
encryption algorithm used. For this reason alone, I would not recommend anyone
to use PGP today. (David Wong)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Not a very positive review, I&#x27;d say. Further points are well-argued, and the
case against PGP is strong. Well, maybe I should rephrase that as &lt;em&gt;the case
against email encryption&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;More fundamental than PGP is the fact that email is a plaintext protocol. It was
never designed with encryption in mind, and that effect has repercussions
towards any solution bolted on top. Cryptographic systems aside, using PGP to
encrypt email means that you&#x27;re still exposing email metadata, even if the
message itself is encrypted.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Despite this, I was still motivated to investigate some of the author&#x27;s
criticisms. Has the world of PGP changed since the book was released in 2021? As
it turns out, yes! Although the details of those changes are themselves worthy
of an unexpectedly deep rabbit hole.&lt;&#x2F;p&gt;
&lt;p&gt;The PGP that &lt;em&gt;Real-World Cryptography&lt;&#x2F;em&gt; discusses is RFC 4880, the 2007 PGP
standard. Efforts to improve this standard have arisen over the last decade, but
have unfortunately split into two separate groups. On one side, GnuPG maintainer
Werner Koch advocates for a conservative approach, spawning LibrePGP. On the
other, Proton, Sequoia, and the IETF advocate for a thorough cryptography
refresh, RFC 9580. The inability for the two sides to agree on one proposal has
left PGP with two.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t want to dive too deep into the semantics between the two different
proposals, since it&#x27;s bad enough that the standard has become plagued with
in-fighting. Instead, I&#x27;ll talk about some of the features that both proposals
bring to the table to address the points mentioned in &lt;em&gt;Real-World
Cryptography&lt;&#x2F;em&gt;.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;authenticated-encryption&quot;&gt;Authenticated encryption&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s start with authenticated encryption:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;PGP does not have authenticated encryption and is, thus, not secure if used
without signatures. (David Wong)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;To clarify the author&#x27;s point, RFC 4880 does include a device for authenticated
encryption known as MDC (Modification Detection Code). However, MDC doesn&#x27;t
provide what the author of &lt;em&gt;Real-World Cryptography&lt;&#x2F;em&gt; would consider
cryptographically strong authentication.&lt;&#x2F;p&gt;
&lt;p&gt;Let me zoom out for a second to explain why any authenticated encryption method
is important for PGP. The attack looks something like this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Alice sends an encrypted mail to Bob. Alice chooses not to sign the message.&lt;&#x2F;li&gt;
&lt;li&gt;Mallory intercepts the encrypted mail. Mallory cannot read the contents of
the message, but they can arbitrarily modify the ciphertext itself. They do
so.&lt;&#x2F;li&gt;
&lt;li&gt;Bob receives the modified ciphertext and decrypts it. Bob has no idea that
the message contents were modified in transit by Mallory. The decrypted
plaintext no longer matches Alice&#x27;s intended message.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Authenticated encryption allows Bob to verify in step 3 whether or not the
ciphertext received is consistent with the original message composed by Alice.
With MDC, Bob decrypts the message, discovers that it has been tampered with,
and knows that there&#x27;s a Mallory sitting in the middle of the correspondence.&lt;&#x2F;p&gt;
&lt;p&gt;Going back to David Wong&#x27;s point, MDC is not considered a sufficient form of
authenticated encryption from a cryptographer&#x27;s standpoint. The verification
code is included within the ciphertext, so when Bob verifies the message in step
3, Bob must first decrypt the entire message. This leaves an open space where a
PGP implementation may accidentally leak message contents and reveal information
due to bad error handling.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In the original spec, the limitations of MDC are considered intentional risk.
Within the RFC, the authors note that they chose MDC because they wanted to
preserve the deniability of messages. MDC achieves this because unlike
alternative authentication mechanisms, there&#x27;s no secret key associating the
authentication to the original message author (Alice).&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenPGP addresses this desire to have more security than raw encryption and
yet preserve deniability with the MDC system. An MDC is intentionally not a
MAC. Its name was not selected by accident. It is analogous to a checksum.
(RFC 4880)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Deniability is an important design goal for PGP simply because signing a message
is optional. If a user chooses not to sign a message, the system should not
implicitly sign said message with its authenticated encryption. Although the
contents of the message should be encrypted, they should not be
cryptographically linked to its author.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily in 2026 we have authenticated encryption mechanisms that both (a)
prevent decryption-oracle attacks and (b) don&#x27;t sacrifice deniability. As Daniel
Huigens explains in his
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;proton.me&#x2F;blog&#x2F;openpgp-crypto-refresh&quot;&gt;crypto-refresh post for Proton Mail&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenPGP has had authenticated encryption for a long time, using a mechanism
called the “Modification Detection Code” (MDC). While this does the job,
modern authenticated encryption schemes achieve secrecy and authentication in
one integrated algorithm. Such encryption modes, dubbed AEAD algorithms, offer
improved performance at the same security level. (Daniel Huigens)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Both LibrePGP and RFC 9580, the two competing modern standards, include AEAD
algorithms that replace the MDC scheme from RFC 4880.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;refreshed-cryptography&quot;&gt;Refreshed cryptography&lt;&#x2F;h2&gt;
&lt;p&gt;It&#x27;s probably clear from the previous section that the two new standards for PGP
address concerns related to obsolete cryptography. Once again citing Daniel
Huigens,&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;[RFC 9580] deprecates old cryptographic primitives and algorithms, including
the hash algorithms MD5, SHA1, and RipeMD, the symmetric algorithms IDEA,
3DES, and CAST5, and the public-key algorithms ElGamal, DSA, and RSA keys of
less than 3072 bits (the security level comparable to Curve25519). These are
all considered less secure than their modern alternatives and, as a result,
are not fit for use in new data or even the consumption of existing data in
some cases. (Daniel Huigens)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That said, the current standards schism between LibrePGP and RFC 9580 is not
helping PGP users in adopting updated cryptographic algorithms. Both specs
introduce a new key format (v5 and v6 respectively) that are incompatible. As a
result, most users are still using v4 keys that use outdated cryptographic
algorithms. The whole thing is a bit of a mess, where v4 keys are an awkward
interoperability layer between the two incompatible modern formats.&lt;&#x2F;p&gt;
&lt;p&gt;The tl;dr is that so long as you&#x27;re using v4 keys, you&#x27;re losing the
cryptographic improvements made in LibrePGP or RFC 9580. Because those v4 keys
are the only compatibility layer between the v5 and v6 proposals, most users are
using them. The vicious cycle ensues.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;looking-forward&quot;&gt;Looking forward&lt;&#x2F;h2&gt;
&lt;p&gt;Realistically I don&#x27;t recommend anyone to go out of their way to set up PGP,
even if it has managed to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;356&#x2F;&quot;&gt;nerd-snipe&lt;&#x2F;a&gt; me over the past
month. You should probably just use Signal.&lt;&#x2F;p&gt;
&lt;p&gt;That said, I think PGP is a great way to start messing around with asymmetric
cryptography to help illustrate the concepts. I recommend
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;book.sequoia-pgp.org&quot;&gt;sq&lt;&#x2F;a&gt;, since it supports RFC 9580 and has excellent
documentation. And hey, if you want to set it up with Thunderbird,
&lt;a href=&quot;&#x2F;words&#x2F;2026-02-14-encrypting-email&#x2F;&quot;&gt;I have a post for that&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Below are the handful of articles I used as reference when writing this post:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc4880&quot;&gt;RFC 4880&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;rfc9580&quot;&gt;RFC 9580&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lwn.net&#x2F;Articles&#x2F;953797&#x2F;&quot;&gt;A schism in the OpenPGP world&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;proton.me&#x2F;blog&#x2F;openpgp-crypto-refresh&quot;&gt;Modernizing and improving PGP security&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Here&#x27;s an example of metadata usage in the real world:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.404media.co&#x2F;proton-mail-helped-fbi-unmask-anonymous-stop-cop-city-protestor&#x2F;&quot;&gt;Proton Mail Helped FBI Unmask Anonymous ‘Stop Cop City’ Protester&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;For the record, I think RFC 9580 is the sensible option. Full support goes
to Proton and their
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;proton.me&#x2F;blog&#x2F;openpgp-crypto-refresh&quot;&gt;efforts to modernize PGP&lt;&#x2F;a&gt;.
If you&#x27;re interested in comparing the two proposals,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.pgpkeys.eu&#x2F;critique-critique.html&quot;&gt;this article&lt;&#x2F;a&gt; offers a
thorough look at both.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;In &lt;em&gt;Real-World Cryptography&lt;&#x2F;em&gt;, David Wong emphasizes that vulnerabilities
caused by mis-implementations are a cryptography problem, because
cryptographic standards should ideally avoid situations where implementation
mistakes lead to vulnerabilities.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Email Encryption with Sequoia PGP and Thunderbird</title>
        <published>2026-02-14T00:00:00+00:00</published>
        <updated>2026-03-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2026-02-14-encrypting-email/"/>
        <id>https://mgmarlow.com/words/2026-02-14-encrypting-email/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2026-02-14-encrypting-email/">&lt;p&gt;The
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discord.com&#x2F;press-releases&#x2F;discord-launches-teen-by-default-settings-globally&quot;&gt;Discord Face ID debacle&lt;&#x2F;a&gt;
has reminded me that centralized platforms routinely abuse user privacy. I can
empathize with the desire to protect underage users, but requiring facial
identification is overreach. It&#x27;s especially insulting when Discord
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discord.com&#x2F;press-releases&#x2F;update-on-security-incident-involving-third-party-customer-service&quot;&gt;leaked some 70,000 government IDs in October 2025&lt;&#x2F;a&gt;
under similar pretenses.&lt;&#x2F;p&gt;
&lt;p&gt;While my friends and I scour the web for an alternative place to call our
digital home, I finally gathered enough motivation to finish setting up
end-to-end encrypted email (via Thunderbird and Sequoia PGP). If you&#x27;re curious
about encrypted email, this post is for you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-encrypt-email&quot;&gt;Why encrypt email?&lt;&#x2F;h2&gt;
&lt;p&gt;The first point to understand is that you don&#x27;t need a good reason to want
encrypted email. Or encrypted anything, for that matter. FSF puts it nicely in
their &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emailselfdefense.fsf.org&#x2F;en&#x2F;&quot;&gt;Email Self-Defense&lt;&#x2F;a&gt; manual:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bulk surveillance violates our fundamental rights and makes free speech risky.
[...] Even if you have nothing to hide, using encryption helps protect the
privacy of people you communicate with, and makes life difficult for bulk
surveillance systems.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I don&#x27;t have any particular &lt;em&gt;need&lt;&#x2F;em&gt; to send encrypted email. I do, however,
believe that privacy is a fundamental right. Personal data should not be
collected or monitored, no matter how innocuous. I want others who feel the same
way to have an easy mechanism for communicating with me under those
circumstances. In the realm of email, that&#x27;s PGP.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;generating-openpgp-keys-with-sequoia-pgp&quot;&gt;Generating OpenPGP keys with Sequoia PGP&lt;&#x2F;h2&gt;
&lt;p&gt;Thunderbird is my email client of choice and offers a few different ways to set
up end-to-end encryption (E2EE):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Generate OpenPGP keys within Thunderbird directly&lt;&#x2F;li&gt;
&lt;li&gt;Import OpenPGP keys generated from an external library (like Sequoia PGP or
GnuPG)&lt;&#x2F;li&gt;
&lt;li&gt;S&#x2F;MIME certificates, which are out of the scope of this article&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This article is about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sequoia-pgp.org&#x2F;&quot;&gt;Sequoia PGP&lt;&#x2F;a&gt;, so I obviously
opted to import my keys instead of using
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;support.mozilla.org&#x2F;en-US&#x2F;kb&#x2F;introduction-to-e2e-encryption#w_how-to-use-openpgp-end-to-end-encryption-with-thunderbird&quot;&gt;Thunderbird OpenPGP&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Sequoia is not the only choice of PGP library. The most widely used library is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnupg.org&#x2F;&quot;&gt;GnuPG&lt;&#x2F;a&gt;, venerable yet often criticized for its
complexity and support for outdated algorithms. I recommend Sequoia because it
promises to be safe by default and opinionated, two phrases that resonate with
me.&lt;&#x2F;p&gt;
&lt;p&gt;Follow these steps to generate a PGP key with Sequoia and import it into
Thunderbird.&lt;&#x2F;p&gt;
&lt;p&gt;Install Sequoia PGP:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;brew&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; install sequoia-sq&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Generate your key:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; key generate --profile rfc4880 --own-key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  --name &amp;quot;Alice Example&amp;quot; --email alice@example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: although I included &lt;code&gt;--profile rfc4880&lt;&#x2F;code&gt; in the command, it&#x27;s technically
redundant since Sequoia uses RFC 4880 by default. I just wanted to reinforce
that Thunderbird follows OpenPGP RFC 4880, not RFC 9580 published in 2024.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;You can use &lt;code&gt;sq key list&lt;&#x2F;code&gt; to view the key you just generated. Take note of the
topmost entry, which provides the 40-character fingerprint that you&#x27;ll use to
identify your key in future steps.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re like me and use a custom domain with multiple email aliases, you need
to register each email alias to the PGP key you just generated. In PGP parlance,
these aliases are called user ids. User ids are included in your key export, so
it&#x27;s important you complete this step before importing your key into
Thunderbird.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Only do this if you have extra email addresses to add to your key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; key userid add --cert&lt;&#x2F;span&gt;&lt;span&gt; $FINGERPRINT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  --name Alice --email alice@work.example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Export your private key that you&#x27;ll later import into Thunderbird:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; key export --cert&lt;&#x2F;span&gt;&lt;span&gt; $FINGERPRINT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; secret.asc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now open up Thunderbird, navigate to Account Settings &amp;gt; End-To-End Encryption,
and import &lt;code&gt;secret.asc&lt;&#x2F;code&gt; via &quot;Add key&quot;. If you have multiple email addresses
configured in Thunderbird, go to &quot;Manage Identities&quot; and configure E2EE for each
one.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;key-exchange&quot;&gt;Key exchange&lt;&#x2F;h2&gt;
&lt;p&gt;Now that you have E2EE enabled, it&#x27;s important to understand how that encryption
actually works. Email encryption is only as good as the contacts you have that
are also using PGP. If you&#x27;re exchanging email with someone not using PGP, your
emails are plaintext regardless of your Thunderbird E2EE setup. PGP
fundamentally requires an exchange of public keys between both participants in
the conversation.&lt;&#x2F;p&gt;
&lt;p&gt;This necessity has lead to networks of public keys known as key servers, e.g.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;keys.openpgp.org&#x2F;&quot;&gt;https:&#x2F;&#x2F;keys.openpgp.org&#x2F;&lt;&#x2F;a&gt;. These are effectively phone books mapping email
addresses to verified public keys. You likely want to upload your own public key
to one such server.&lt;&#x2F;p&gt;
&lt;p&gt;Within Thunderbird, you can discover public keys while composing an email via
the OpenPGP Key Assistant. Alternatively, you can search with Sequoia:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; network search alice@example.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If Sequoia successfully locates a key, it follows up with a helpful message
prompting you to verify the integrity of the key. The intent of this step is to
verify with the participant that the certificate indeed belongs to them.
Typically this happens via an alternative method of communication.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# I verify that this fingerprint belongs to Alice&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; pki link add --cert=&lt;&#x2F;span&gt;&lt;span&gt;$FINGERPRINT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  --userid=&amp;quot;Alice &amp;lt;alice@example.com&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I won&#x27;t dive too far into the details, but the nature of key servers and key
verification is widely known as the Web of Trust. It&#x27;s good practice to verify
the keys of your PGP contacts because the act is effectively vouching for the
authenticity of that key.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;updates-to-this-post&quot;&gt;Updates to this post&lt;&#x2F;h2&gt;
&lt;p&gt;A prior version of this post used GnuPG instead of Sequoia PGP, following the
FSF guide &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emailselfdefense.fsf.org&#x2F;en&#x2F;&quot;&gt;Email Self-Defense&lt;&#x2F;a&gt;. Since
then, I&#x27;ve migrated to Sequoia PGP and use SSH instead of GPG to sign my git
commits.&lt;&#x2F;p&gt;
&lt;p&gt;The reasons for this change have to do with my recent discovery of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lwn.net&#x2F;Articles&#x2F;953797&#x2F;&quot;&gt;A schism in the OpenPGP world&lt;&#x2F;a&gt; and its
implications for PGP as a cryptographic standard. I plan to detail my thoughts
on the subject in a future post, after I gather more research around the crazy
world of PGP RFCs and the divergence of LibrePGP and OpenPGP.&lt;&#x2F;p&gt;
&lt;p&gt;I think it&#x27;s unfortunate that the already vanishingly small number of PGP users
are further divided between the goal of backwards compatibility and modern
cryptographic techniques. I tend to side with the authors of Sequoia PGP and
their goal of a safe-by-default, opinionated implementation.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;The notion of fingerprint verification is core to asymmetric cryptography.
For example, Signal likewise has a verification process through
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;signal.org&#x2F;blog&#x2F;safety-number-updates&#x2F;&quot;&gt;safety numbers&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Coding with Agents; Managers and Builders</title>
        <published>2026-02-05T00:00:00+00:00</published>
        <updated>2026-02-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2026-02-05-coding-with-agents/"/>
        <id>https://mgmarlow.com/words/2026-02-05-coding-with-agents/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2026-02-05-coding-with-agents/">&lt;p&gt;Like many software engineers I&#x27;ve been experimenting with coding agents as part
of my day job.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; My approach is still in its infancy: a handful of tabs and
sessions organized by tmux, terminal split in half while coaching Claude through
planning and implementation, mostly focusing on just one task at a time. I
haven&#x27;t seen the productivity boost lauded by tech executives, but I remain
impressed by the kinds of things Claude Code gets right.&lt;&#x2F;p&gt;
&lt;p&gt;The more I work with agents, the more my job becomes centered around reviewing
code. And reviewing code is difficult! Properly reviewing a diff requires
acclimating within the headspace of the proposed change and understanding its
tradeoffs. Since I don&#x27;t participate in the act of building, I lack the context
and theory behind the change. So when I review the code, simply reading the diff
isn&#x27;t sufficient.&lt;&#x2F;p&gt;
&lt;p&gt;The situation is a little different with AI agents because I drive the upfront
planning and problem description. In theory, I have the context of the change,
and even if I lack the process of typing it I have a general idea of how it came
to be. But in practice, coding with AI still feels a lot more like reviewing a
pull request than it does building a feature.&lt;&#x2F;p&gt;
&lt;p&gt;All this to say, my day-to-day workflow is catered more towards reviewing code
than writing code. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tmux&#x2F;tmux&#x2F;wiki&quot;&gt;tmux&lt;&#x2F;a&gt; is absolutely
essential because it allows me to manipulate my terminal to prompt agents,
investigate implementation details, or run one-off tasks.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dandavison&#x2F;delta&quot;&gt;delta&lt;&#x2F;a&gt; is the newest addition to my kit,
improving the look of &lt;code&gt;git diff&lt;&#x2F;code&gt; since I now scrutinize every line of code in my
working tree before wrapping it together into a commit.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;BurntSushi&#x2F;ripgrep&quot;&gt;ripgrep&lt;&#x2F;a&gt; and other CLI interfaces are
essential for crossing the gap between agent and manual work.&lt;&#x2F;p&gt;
&lt;p&gt;As I work more with AI tools, my feelings towards programming have grown more
complicated. The shift from mostly writing code to mostly writing product
specifications is not one I&#x27;m particularly happy about. For many, coding is
nothing but an obstacle in the way of a successful product. For me, coding is
artistic expression. Building programs and structuring code in a maintainable
way is fun! Hell, editing text is fun.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; Spending less time writing code and
more time writing specs or reviewing changesets has been a difficult transition.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve seen others mention this shift as a schism between builders and managers.
Folks who prefer composing instructions that delegate to agents, AI or human,
are managers. Folks who prefer typing text into an editor and watching programs
come alive are builders. Managers love the workflows AI tools enable. Builders
lament them. I won&#x27;t go so far as to say I lament them, but more and more I find
myself avoiding AI tools on my personal projects.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m also worried that AI causes skill to atrophy. Anthropic recently released a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2601.20245&quot;&gt;paper studying the impact of AI on skill formation&lt;&#x2F;a&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;
in junior developers and the results do not look good. What this means for the
industry in coming years is an unnerving thought.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;For the record, I staunchly refuse to use AI for any of the writing on this
blog. Or really, any of my creative endeavors. I see AI merely as a
productivity tool, not a substitute for creativity.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;It helps when you practice fancy keybindings, adding an extra layer of
problem solving to the process of refining text in an editor.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;Anthropic&#x27;s research into these kinds of topics is exactly why I&#x27;m happy to
support their AI tools.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Prototyping a Chess Puzzle Game</title>
        <published>2026-01-10T00:00:00+00:00</published>
        <updated>2026-01-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2026-01-08-monster-chess/"/>
        <id>https://mgmarlow.com/words/2026-01-08-monster-chess/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2026-01-08-monster-chess/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;monster-chess-bud.pages.dev&#x2F;&quot;&gt;Monster Chess&lt;&#x2F;a&gt; is my attempt at a chess
puzzle game that doesn&#x27;t require any knowledge of chess except the movement of
the pieces.&lt;&#x2F;p&gt;
&lt;p&gt;Rules of Monster Chess:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You can only move white pieces.&lt;&#x2F;li&gt;
&lt;li&gt;Capturing a piece turns your current piece into the kind that you captured.&lt;&#x2F;li&gt;
&lt;li&gt;Pawns do not promote on the back rank.&lt;&#x2F;li&gt;
&lt;li&gt;You can refresh the page to reset the level.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There are 12 puzzles in all.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;monster-chess-bud.pages.dev&#x2F;&quot;&gt;Give them a try&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;Read on to learn how the game was made.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;creating-monster-chess&quot;&gt;Creating Monster Chess&lt;&#x2F;h2&gt;
&lt;p&gt;I love chess tactics puzzles, but I hate playing chess. It&#x27;s a very
contradictory hobby.&lt;&#x2F;p&gt;
&lt;p&gt;Unfortunately, to solve a chess puzzle one must have a deep understanding of the
game of chess. Not just the movement of the pieces, mind you, but various
tactics that make up fundamental strategy. Mating puzzles are the most obvious,
but deeper into the realms of chess puzzles are forks, pins, skewers,
sacrifices, discovered checks, zugzwang, and so on. Enjoying a chess puzzle is
entirely hinged on knowing why a particular sequence of moves is the single
correct answer. Not an easy feat!&lt;&#x2F;p&gt;
&lt;p&gt;Despite this, chess puzzles are beautifully expressive. A board position just
begs to be solved, each piece artfully contributing constraints to the
possibility space. White to move, mate in 2.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;morphy-mate-2.png&quot; alt=&quot;Morphy Mate in 2&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So, I got to thinking. What would a chess puzzle look like if it didn&#x27;t assume
any prior knowledge of chess, except for the movement of the pieces?&lt;&#x2F;p&gt;
&lt;p&gt;The first thing I removed was the opponent. If we&#x27;re no longer playing the game
of chess, but simply using the pieces of chess to define a puzzle language, the
need for opponent interactions are gone. No more deducing opponent reactions in
response to a move.&lt;&#x2F;p&gt;
&lt;p&gt;I recall Puzzmo&#x27;s &lt;a href=&quot;&#x2F;words&#x2F;2025-01-11-paper-puzzle-remixes&#x2F;&quot;&gt;paper puzzle remix&lt;&#x2F;a&gt;
of Really Bad Chess. It tweaked the classic mating puzzle format by introducing
multiple kings into a single board position and asking for unique checkmate
combinations by different pieces. It was a unique spin on the format, but
retained too much chess tactic DNA for the result I wanted.&lt;&#x2F;p&gt;
&lt;p&gt;Without an opponent, tactic puzzles fall apart. Mating puzzles are fun, but
lacking originality. This is when I stumbled into the central puzzle idea for
Monster Chess: the piece that captures morphs into the one taken captive.&lt;&#x2F;p&gt;
&lt;p&gt;With the main idea out of the way, it didn&#x27;t take long to pin down the rest of
the puzzle constraints. The win condition becomes very natural: clear the board
of enemy pieces. Building interesting puzzles means constraining that movement
set with pieces that are limited, like pawns. Each puzzle is focused on ensuring
the player executes the proper sequence of moves else they get stuck in an
irredeemable position.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lichess-inspired-tech-stack&quot;&gt;Lichess-inspired tech stack&lt;&#x2F;h2&gt;
&lt;p&gt;Around the time I was thinking about Monster Chess I was investigating the
Lichess frontend architecture. Lichess doesn&#x27;t use a popular frontend framework,
like React or Vue. Instead, it uses a tiny virtual DOM (VDOM) library called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;snabbdom&#x2F;snabbdom&quot;&gt;snabbdom&lt;&#x2F;a&gt;. The architecture that stitches
the snabbdom views, game logic, and server-rendered data together is custom-made
by Lichess.&lt;&#x2F;p&gt;
&lt;p&gt;I can speculate why Lichess chose snabbdom over alternatives:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Lichess predates modern frameworks&lt;&#x2F;li&gt;
&lt;li&gt;Performance is key, fewer framework layers are a positive&lt;&#x2F;li&gt;
&lt;li&gt;Maintainability takes precedence over shiny tools&lt;&#x2F;li&gt;
&lt;li&gt;Lichess has unique UI considerations&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Then again, why bother with a VDOM library at all?&lt;&#x2F;p&gt;
&lt;p&gt;I think the answer boils down to philosophical alignment with the Scala backend.
Vanilla JS (or jQuery, which would&#x27;ve been more relevant in the 2010s) is
fundamentally imperative. It&#x27;s up to the developer to figure out how they want
to stitch together state and UI, and that process often involves wrapping state
updates with targeted DOM mutations.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a simple example comparing a vanilla JS counter to a snabbdom counter.&lt;&#x2F;p&gt;
&lt;p&gt;Vanilla JS:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;counter&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Count: 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&amp;lt;!-- UI updates live a ways away from the DOM --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; document&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;counter&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;addEventListener&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;click&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; Targeted DOM mutation in response to a state update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;textContent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `Count: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Snabbdom:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; container&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; document&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;app&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; UI = f(state)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; view&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; h&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    &amp;#39;button&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      on&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        click&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;          render&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    `Count: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; The library handles figuring out which bits of state&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; correspond to which DOM updates&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; render&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  container&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; patch&lt;&#x2F;span&gt;&lt;span&gt;(container&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; view&lt;&#x2F;span&gt;&lt;span&gt;(count))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Snabbdom allows the UI to be represented declaratively as a function of state.
Re-rendering concerns the entire application, not just a single unit of the DOM.
Developers don&#x27;t need to worry about applying DOM updates as a result of state
changes; instead they write their UI, write their application logic, and let
re-renders handle the rest.&lt;&#x2F;p&gt;
&lt;p&gt;As you might expect, re-rendering the entire DOM tree would be prohibitively
expensive and wasteful. Hence the virtual DOM. Snabbdom figures out which DOM
nodes need to be updated based on state changes, using the VDOM diff as a guide.
Then it updates only the necessary nodes.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;flux-architecture-for-snabbdom&quot;&gt;Flux architecture for snabbdom&lt;&#x2F;h3&gt;
&lt;p&gt;Zooming out a bit, it&#x27;s important to note that snabbdom is just a VDOM library.
It does not prescribe a technique for managing state in your application or a
philosophy around triggering re-renders. That&#x27;s the realm of a framework.&lt;&#x2F;p&gt;
&lt;p&gt;Lichess organizes state into
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;lichess-org&#x2F;lila&#x2F;blob&#x2F;1247bf68450954db5b7b23a727e275e07ebb401a&#x2F;ui&#x2F;editor&#x2F;src&#x2F;editor.ts#L12&quot;&gt;controllers&lt;&#x2F;a&gt;.
A simplified example looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; EditorCtrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  castlingRights&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; CastlingRights&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  onChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; Update state&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;castlingRights&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; computeCastlingRights&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; And trigger a re-render&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rerender&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; UI = f(ctrl)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; view&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;ctrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; EditorCtrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; h&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;div.board-editor&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      on&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        click&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ctrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;onChange&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Initialize snabbdom onto the current page&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; initModule&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Container for game state and logic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; ctrl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; EditorCtrl&lt;&#x2F;span&gt;&lt;span&gt;(rerender)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; First render pass to generate initial DOM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; el&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; document&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;board-editor&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; vnode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; patch&lt;&#x2F;span&gt;&lt;span&gt;(el&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; view&lt;&#x2F;span&gt;&lt;span&gt;(ctrl))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Trigger re-renders to update the UI after a state change&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; rerender&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    vnode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; patch&lt;&#x2F;span&gt;&lt;span&gt;(vnode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; view&lt;&#x2F;span&gt;&lt;span&gt;(ctrl))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For the purposes of Monster Chess, I didn&#x27;t want to worry about manually
triggering re-renders on state changes. Any meaningful action in Monster Chess
is going to result in a UI update, so I can simply re-render indiscriminately.
So, I took a few liberties with the Lichess style of things by adapting the data
flow to something that resembles
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;facebookarchive.github.io&#x2F;flux&#x2F;docs&#x2F;in-depth-overview&quot;&gt;Flux&lt;&#x2F;a&gt; (so 2014,
dude).&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a simplified counter example, demonstrating the structure:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Store&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; If you don&amp;#39;t like mutation, you could always go redux-style:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; this.state = reduce(action)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;action&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    switch&lt;&#x2F;span&gt;&lt;span&gt; (action&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;type)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      case&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;inc&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;        this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        break&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      case&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;dec&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;        this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;--&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        break&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; view&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;dispatch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; h&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;div&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    h&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;p&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `count: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    h&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;button&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      on&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; click&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; dispatch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;inc&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;+&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    h&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;button&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      on&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; click&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; dispatch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;dec&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;-&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Bootstrap boilerplate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;window&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;addEventListener&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;DOMContentLoaded&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; container&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; document&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;querySelector&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;#app&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; store&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Store&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; When actions are dispatched to the store, we always&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; re-render afterwards.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; dispatch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;action&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Action&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    store&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span&gt;(action)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    render&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; makeView&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; view&lt;&#x2F;span&gt;&lt;span&gt;(dispatch)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; vnode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; patch&lt;&#x2F;span&gt;&lt;span&gt;(container&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; makeView&lt;&#x2F;span&gt;&lt;span&gt;(store&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;state))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; render&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    vnode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; patch&lt;&#x2F;span&gt;&lt;span&gt;(vnode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; makeView&lt;&#x2F;span&gt;&lt;span&gt;(store&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;state))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The view is constructed with two parameters, &lt;code&gt;dispatch&lt;&#x2F;code&gt; and &lt;code&gt;state&lt;&#x2F;code&gt;. &lt;code&gt;dispatch&lt;&#x2F;code&gt;
passes messages through the store and triggers a re-render. During the render
pass, snabbdom diffs the VDOM and applies changes to the UI based on the most
recent value of &lt;code&gt;state&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m sure that Lichess benefits from more control over the render lifecycle of
its application, but I definitely appreciate the simplicity of Flux. The view
doesn&#x27;t call directly into a stateful controller, it merely reads state for a
render pass and dispatches the occasional action back into the store.&lt;&#x2F;p&gt;
&lt;p&gt;To put things into more concrete terms, here&#x27;s a view into Monster Chess&#x27;s
&lt;code&gt;Action&lt;&#x2F;code&gt; type, demonstrating all of the events that are dispatched by the view:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Action&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;change_mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; payload&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;select&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; } }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;select_level&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; payload&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; level&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; } }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;select_piece&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; payload&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; piece&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Piece&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; } }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;deselect&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;      type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;move&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;      payload&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;        to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Square&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;        original&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Piece&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;        captured&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Piece&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-style: italic;&quot;&gt; undefined&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;why-not-just-use-react&quot;&gt;Why not just use React?&lt;&#x2F;h3&gt;
&lt;p&gt;Simply put: novelty. I wanted to see how far I could take a VDOM library without
all of the extra framework primitives.&lt;&#x2F;p&gt;
&lt;p&gt;That said, I would generally avoid React for projects like Monster Chess. React
is the kind of tool that is acceptable across the board, but doesn&#x27;t excel in
any particular category. Its main draw is mindshare, reducing the number of
decisions made by large teams. Its API is complicated (&lt;code&gt;useEffect&lt;&#x2F;code&gt; dependencies,
anyone?) and its VDOM algorithm is now a complicated concurrent scheduler. For
tiny web games it&#x27;s overkill.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re interested in making your own games with this snabbdom stack, check
out my &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;simple-game-template&quot;&gt;game template&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Guide to Cryptic Crosswords</title>
        <published>2025-11-03T00:00:00+00:00</published>
        <updated>2025-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-11-03-beginner-guide-to-cryptics/"/>
        <id>https://mgmarlow.com/words/2025-11-03-beginner-guide-to-cryptics/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-11-03-beginner-guide-to-cryptics/">&lt;p&gt;The cryptic is a special kind of crossword that is unfortunately lacking
popularity in the US. Unlike it&#x27;s American counterpart (e.g. the NYT daily),
every clue in a cryptic utilizes some kind of misleading wordplay. The result is
a crunchy (and hilarious) word puzzle that is a delight to solve.&lt;&#x2F;p&gt;
&lt;p&gt;To the uninitiated, the cryptic crossword is downright indecipherable. To the
initiated, well, it&#x27;s often just as confusing. But therein lies the fun of the
cryptic: every clue is its own little mini-puzzle.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example cryptic clue, plucked from a recent
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.minutecryptic.com&#x2F;&quot;&gt;Minute Cryptic&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Golf lesson&#x27;s beginning in month six for Tiger Woods? (6)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The answer is JUNGLE. Confused? Good.&lt;&#x2F;p&gt;
&lt;p&gt;On the surface, a cryptic clue is a humorous phrase with little to no relation
to its answer. Hiding within is a meta-textual puzzle composed of each word of
the clue, a recipe that points the solver to the solution. This recipe often
consists of the following ingredients:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Definition&lt;&#x2F;strong&gt;, the straightforward meaning of the solution&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fodder&lt;&#x2F;strong&gt;, the letters you will play around with to reach the solution&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wordplay&lt;&#x2F;strong&gt; indicator(s), hinting at what to do with the Fodder to reach the
Definition&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Length indicator&lt;&#x2F;strong&gt;, a parenthetical showing how many letters and words are
in the solution&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Not every cryptic clue will contain every one of these elements, but the
majority of clues incorporate at least a few. Let&#x27;s look at another example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tide turns to change (4)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: EDIT&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A surface reading of the clue suggests a shoreline changing tides. It should
come as no surprise that water has nothing to do with the actual 4-lettered
solution.&lt;&#x2F;p&gt;
&lt;p&gt;Breaking the clue into its components provides:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Definition&lt;&#x2F;strong&gt;: change&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fodder&lt;&#x2F;strong&gt;: tide&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wordplay indicator&lt;&#x2F;strong&gt;: turns&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Length&lt;&#x2F;strong&gt;: a single, 4-letter word&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Recognizing a clue&#x27;s recipe takes time and practice. In fact, the vast majority
of time spent solving a cryptic is trying to figure out the correct recipe!
Often a tough clue will hint at several possible devices, of which only one is
the proper answer.&lt;&#x2F;p&gt;
&lt;p&gt;The definition part of a clue often appears at the beginning or the end, as is
the case with the above. This means &quot;tide&quot; or &quot;change&quot; are the most likely
suspects.&lt;&#x2F;p&gt;
&lt;p&gt;From there, look through the remaining elements for some sign of wordplay. &quot;To&quot;
isn&#x27;t a good guess, there&#x27;s not much room for anything interesting. &quot;Turn&quot;, on
the other hand, can be taken a few different ways. It can mean &quot;change&quot;, as is
consistent with the surface-reading of the clue (indicating changing tides). Or,
taken literally, &quot;turn&quot; can mean &quot;swap direction&quot; or &quot;reverse&quot;. Wordplay
indicator located.&lt;&#x2F;p&gt;
&lt;p&gt;Next up: which word is reversed? With the two suspects &quot;tide&quot; and &quot;change&quot;, only
one word is exactly 4 letters. Therefore, &lt;strong&gt;reverse &quot;tide&quot; to find &quot;edit&quot;, a
word that means &quot;change&quot;&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Turns is just one of hundreds of wordplay indicators that you’ll find out in the
wild. It belongs to a category of &quot;reversals&quot;, indicators that require reversing
the Fodder to reach the solution. A few other indicators belonging to the same
category are &quot;reversed&quot;, &quot;goes back&quot;, or &quot;in retrospect&quot;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;categories-of-wordplay&quot;&gt;Categories of wordplay&lt;&#x2F;h2&gt;
&lt;p&gt;The different kinds of wordplay that you might see in a cryptic crossword can
often be grouped into broader categories. You&#x27;ve already seen &lt;strong&gt;Reversals&lt;&#x2F;strong&gt;,
wordplay that reverses the letters of a word. Here are some other examples of
cryptic wordplay techniques.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;anagrams&quot;&gt;Anagrams&lt;&#x2F;h3&gt;
&lt;p&gt;Rearrange letters to form a new word.&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: destroyed, messed up, rearranged.&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Wine with no tip? Crazy (5)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: PINOT. Parsed as NO TIP annagrammed (made crazy).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;deletions&quot;&gt;Deletions&lt;&#x2F;h3&gt;
&lt;p&gt;Remove one or more letters from a word.&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: removed, defaced, dropped off.&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Trim tabs for six pack (3)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: ABS. Parsed as TABS minus the first letter (trim).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;hidden-words&quot;&gt;Hidden words&lt;&#x2F;h3&gt;
&lt;p&gt;Find a word hidden within one or several other words.&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: hides, contains.&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;World&#x27;s tallest mountain home hidden in fine palace (5)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: NEPAL. Parsed as hidden in FINE PALACE.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;soundalikes&quot;&gt;Soundalikes&lt;&#x2F;h3&gt;
&lt;p&gt;Swap one word for another that sounds similar (e.g. homophone).&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: broadcasted, said, announced.&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disregard mumbled greeting (5)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: WAIVE. Parsed as homophone (mumbled) of WAVE (greeting).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;charades&quot;&gt;Charades&lt;&#x2F;h3&gt;
&lt;p&gt;Substitute one word for another.&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: none required (this one is tricky!)&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pass standard edition, no longer active (5)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: LAPSE. Parsed as LAP (pass) SE (standard edition).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;containments&quot;&gt;Containments&lt;&#x2F;h3&gt;
&lt;p&gt;Place one word inside of another.&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: accepts, infiltrates, sandwiches (example from Minute Cryptic).&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Golf lesson&#x27;s beginning in month six for Tiger Woods? (6)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: JUNGLE. Parsed as first letters of GOLF LESSON (beginning) placed
within (in) JUNE (month six).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;double-definitions&quot;&gt;Double definitions&lt;&#x2F;h3&gt;
&lt;p&gt;Two definitions for the same word.&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: none required.&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Medicine sheds fluff (5)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: PILLS. Parsed as sheds fluff as in a sweater.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;all-of-the-above&quot;&gt;All of the above&lt;&#x2F;h3&gt;
&lt;p&gt;Nothing limits a cryptic crossword clue to a single wordplay device. Most clues
will combine several wordplay techniques to make more interesting words and
combinations.&lt;&#x2F;p&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Seasons every other shallot with top spice (5)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: SALTS. Parsed as every other letter in SHALLOT + first letter (top) of
SPICE.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;And another:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prepare ambush: half partners, half betrayal, full reversal (3,1,4)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: LAY A TRAP. Parsed as PARTners (half) + betrAYAL (half) reversed.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;lit&quot;&gt;&amp;amp;lit&lt;&#x2F;h3&gt;
&lt;p&gt;Pronounced &quot;and lit&quot;, a curious construction. &amp;amp;lit clues do not have a separate
definition. Instead, the entire clue is considered the definition. It&#x27;s best to
demonstrate with an example (this one comes from Minute Cryptic):&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cop in male form! (9)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Answer: POLICEMAN.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The answer &quot;POLICEMAN&quot; fits the entire clue, a &quot;cop in male form&quot;. It&#x27;s also
achieved via the anagram indicator &quot;form&quot; that suggests rearranging &quot;cop in
male&quot;. Sneaky!&lt;&#x2F;p&gt;
&lt;p&gt;Indicators: none needed, although many outlets like Minute Cryptic will end the
clue with &quot;?!&quot; to help indicate that an &amp;amp;lit is in play.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;learning-to-learn&quot;&gt;Learning to learn&lt;&#x2F;h2&gt;
&lt;p&gt;It can be difficult to recognize wordplay techniques when you&#x27;re first getting
started with cryptics. Here are a few tips and resources to help ease the path:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.minutecryptic.com&#x2F;&quot;&gt;Minute Cryptic&lt;&#x2F;a&gt; is an accessible cryptic
crossword outlet that releases a new clue every day. Start here!&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;When solving, don&#x27;t be afraid to reveal an answer to reverse-engineer the
wordplay. That&#x27;s a great way to learn your way around the different wordplay
devices!&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.theguardian.com&#x2F;crosswords&quot;&gt;Guardian&lt;&#x2F;a&gt; offers a free,
introductory puzzle called the Quick Cryptic (not to be confused with the
&quot;Quiptic&quot;, which is harder). Be forewarned that there may be one or two
British idioms in any given puzzle.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.fifteensquared.net&quot;&gt;Fifteensquared&lt;&#x2F;a&gt; collects detailed answers
(AKA parses) for popular British cryptic crosswords. If you&#x27;re solving the
Guardian (among others), this site is a great reference.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You can find the cryptic crosswords I&#x27;ve constructed &lt;a href=&quot;&#x2F;crosswords&quot;&gt;over here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Why React Matters</title>
        <published>2025-10-26T00:00:00+00:00</published>
        <updated>2025-10-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-10-26-why-react-matters/"/>
        <id>https://mgmarlow.com/words/2025-10-26-why-react-matters/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-10-26-why-react-matters/">&lt;p&gt;A recent popular article compares
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;backbonenotbad.hyperclay.com&#x2F;&quot;&gt;React to Backbone&lt;&#x2F;a&gt;, arguing that React
is not much of an improvement. I think this article profoundly misses the point.
And I don&#x27;t even like React!&lt;&#x2F;p&gt;
&lt;p&gt;Before discussing what the article gets wrong, I want to quickly mention what it
gets right. These bullet points captured from the article are a great list of
React-isms that are worthy of criticism:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;key&lt;&#x2F;code&gt; as a mandatory prop for lists is fraught&lt;&#x2F;li&gt;
&lt;li&gt;form inputs cannot initialize with &lt;code&gt;undefined&lt;&#x2F;code&gt;, drawing a strange division
between controlled and uncontrolled controls&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;useEffect&lt;&#x2F;code&gt; dependency arrays are difficult to understand&lt;&#x2F;li&gt;
&lt;li&gt;batched updates lead to awkward APIs, like the callback form of &lt;code&gt;setState&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;React has many rough edges that are difficult for beginners to wrap their heads
around, particularly &lt;code&gt;useEffect&lt;&#x2F;code&gt; and all of the intricacies of dependency
management. I can&#x27;t tell you how many React developers have made the mistake of
syncing state via effects when they could simply derive it with a variable
assignment.&lt;&#x2F;p&gt;
&lt;p&gt;That said, the article hand-waves over the main reason this complexity exists:
the ability to represent UI as a function of state. Or, put another way,
unidirectional data flow.&lt;&#x2F;p&gt;
&lt;p&gt;The Backbone example provided by the article clearly demonstrates the lack of
this ability. Below is the Backbone render function, tasked with assembling the
initial UI of the view:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  render&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;$el&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;      &amp;lt;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        &amp;lt;input type=&amp;quot;password&amp;quot; placeholder=&amp;quot;Enter password&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        &amp;lt;div class=&amp;quot;space-y-2&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;          ${&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;reqs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;label&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;            &amp;lt;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;              &amp;lt;div&amp;gt;&amp;lt;&#x2F;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;              &amp;lt;span&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;${&lt;&#x2F;span&gt;&lt;span&gt;label&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;lt;&#x2F;span&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;            &amp;lt;&#x2F;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          `)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;.join(&amp;#39;&amp;#39;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        &amp;lt;&#x2F;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;      &amp;lt;&#x2F;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    `&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the event handler that triggers on changes made to the input:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  updatePassword&lt;&#x2F;span&gt;&lt;span&gt;(e)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span&gt; pwd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span&gt; reqs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;.space-y-2&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span&gt;(reqs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;([&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;label&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; met&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; `&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;      &amp;lt;div class=&amp;quot;flex items-center gap-2&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        &amp;lt;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;          ${&lt;&#x2F;span&gt;&lt;span&gt;met&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;✓&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        &amp;lt;&#x2F;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        &amp;lt;span&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;          ${&lt;&#x2F;span&gt;&lt;span&gt;label&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;        &amp;lt;&#x2F;span&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;      &amp;lt;&#x2F;div&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    `&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;join&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notice that the event handler is tasked with a localized DOM manipulation. When
the input change event fires, the handler queries the DOM and replaces the HTML
in-place. The details of the UI are now contained in two different places: the
&lt;code&gt;updatePassword&lt;&#x2F;code&gt; event handler and the &lt;code&gt;render&lt;&#x2F;code&gt; function.&lt;&#x2F;p&gt;
&lt;p&gt;This kind of separation is exactly what the pre-React world struggled to
maintain. When state is kept separate from the UI, yet requires that the UI is
manually updated when that state is changed, complications ensue.&lt;&#x2F;p&gt;
&lt;p&gt;Contrast this with the React solution, where the developer is tasked with
constructing state variables (via &lt;code&gt;useState&lt;&#x2F;code&gt;) that update the UI automatically:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; PasswordStrength&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;password&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; setPassword&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; requirements&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;input&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;        type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;password&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;        value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;password&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;        onChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; setPassword&lt;&#x2F;span&gt;&lt;span&gt;(e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;        placeholder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Enter password&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; className&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;space-y-2&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        {&lt;&#x2F;span&gt;&lt;span&gt;requirements&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;req&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;          const&lt;&#x2F;span&gt;&lt;span&gt; isMet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; req&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span&gt;(password)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;          return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;            &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;              &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;isMet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;✓&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;              &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;span&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;req&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;label&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;span&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;            &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The most notable difference between the two approaches takes place in the event
handler. In React, the handler only updates the underlying component state. The
UI is automatically updated by the framework. I think it&#x27;s hard to argue that
this kind of unidirectional data flow isn&#x27;t a large improvement over Backbone.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;&#x2F;strong&gt;: LOC comparisons are meaningless.&lt;&#x2F;p&gt;
&lt;p&gt;I think a more interesting comparison is to consider the paradigm of
unidirectional data flow outside of React. If I want to render HTML as a
function of state, but avoid the complexities of React, what are my options?&lt;&#x2F;p&gt;
&lt;p&gt;Web components are a natural place to look, since they&#x27;re the HTML standard for
UI components. Luckily for us
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;plainvanillaweb.com&#x2F;index.html&quot;&gt;Plain Vanilla Web&lt;&#x2F;a&gt; already provides a
comparison against React:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;plainvanillaweb.com&#x2F;blog&#x2F;articles&#x2F;2024-09-28-unreasonable-effectiveness-of-vanilla-js&#x2F;&quot;&gt;The unreasonable effectiveness of vanilla JS&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Notably on the topic of unidirectional data flow, the author of Plain Vanilla
Web has this to say:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Because there is no framework patching the DOM with only the parts that
changed, we have to tread lightly and only update the DOM when and where that
is needed. Recreating too much of the DOM after a state change risks losing
state or causing performance issues.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;And here we encounter the first rub of implementing unidirectional data flows:
re-rendering the entire application as a function of state is expensive! This
problem is exactly why React uses a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;legacy.reactjs.org&#x2F;docs&#x2F;reconciliation.html&quot;&gt;virtual DOM&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re willing to adopt a library, there are a ton of compelling options.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lichess.org&quot;&gt;Lichess&lt;&#x2F;a&gt; famously uses
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;snabbdom&#x2F;snabbdom&quot;&gt;snabbdom&lt;&#x2F;a&gt;, which is effectively a virtual
DOM without a framework. If you like the look of HTML template strings,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lit.dev&#x2F;docs&#x2F;v1&#x2F;lit-html&#x2F;introduction&#x2F;&quot;&gt;lit-html&lt;&#x2F;a&gt; might be your jam.
Or, how about ditching the virtual DOM in favor of a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;svelte.dev&#x2F;&quot;&gt;compiler&lt;&#x2F;a&gt; that writes localized DOM manipulations for you?&lt;&#x2F;p&gt;
&lt;p&gt;All this to say, React is a solution built for web applications that are wholly
controlled by JavaScript. Server-rendered apps can and should opt for the
simpler solution of progressive enhancement because the scope of localized DOM
manipulations is much smaller when the server is tasked with re-rendering the
HTML. But please, don&#x27;t use Backbone.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A Thorough Look into RBS for Rails</title>
        <published>2025-10-18T00:00:00+00:00</published>
        <updated>2025-10-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-09-21-brief-look-into-rbs-rails/"/>
        <id>https://mgmarlow.com/words/2025-09-21-brief-look-into-rbs-rails/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-09-21-brief-look-into-rbs-rails/">&lt;p&gt;Back in 2020, Ruby 3.0 introduced a system for writing type definitions for Ruby
programs called RBS. Five years later, I rarely hear about it being used in
production Rails apps.&lt;&#x2F;p&gt;
&lt;p&gt;The situation is somewhat similar to Ractors, another feature released with Ruby
3.0. The idea of Ractors brought a ton of buzz to the Ruby community because it
directly answered one of the major pain points of the language: sidestepping the
GVL for real parallelism. However, the reality of Ractors and the semantics of
shareable objects made them nearly impossible to adopt.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t think RBS is in the same boat. At first blush, the toolset feels usable
and sophisticated, if lacking a bit in maturity. My experimentation with it has
already yielded benefit in a real-world Rails application. Adoption in an
existing codebase isn&#x27;t trivial, but the semantics of RBS make it possible to do
incrementally.&lt;&#x2F;p&gt;
&lt;p&gt;One of the main problems I see with RBS coverage on the web is that it&#x27;s heavily
catered towards RubyGems or vanilla Ruby. There are not many articles that
explain RBS in Rails beyond a few cursory comparisons with Sorbet. This post
aims to fill that gap.&lt;&#x2F;p&gt;
&lt;p&gt;Overall, I think RBS is in a fairly good spot. It still has rough edges, but
many of those edges are intrinsic to the nature of Ruby metaprogramming. Just
like with TypeScript and JavaScript, static types will change the way that you
write Ruby code to guarantee states and branches that are analyzable by static
tools. Whether that&#x27;s good or bad depends on taste.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s dive in.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rbs-tools&quot;&gt;RBS tools&lt;&#x2F;h2&gt;
&lt;p&gt;When working with RBS in a Rails codebase, you will make ample use of the
following tools:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ruby&#x2F;rbs&quot;&gt;rbs&lt;&#x2F;a&gt;, used for signature prototyping (AKA code
generation) and managing signatures for RubyGems. I will refer to the gem from
now on as &lt;code&gt;rbs&lt;&#x2F;code&gt; and the general signature format as RBS.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;soutaro&#x2F;steep&quot;&gt;Steep&lt;&#x2F;a&gt;, the gem that actually performs
type-checking.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pocke&#x2F;rbs_rails&quot;&gt;rbs_rails&lt;&#x2F;a&gt;, a handy set of Rake tasks to
help generate ActiveRecord signatures.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It&#x27;s important to note that &lt;code&gt;rbs&lt;&#x2F;code&gt; does not actually perform type-checking. It
only defines the syntax that you will use for writing signature files and
provides an API for accessing those signatures programmatically. Steep is the
program that actually checks if the types are valid.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;rbs&lt;&#x2F;code&gt; does, however, provide some handy CLI tools that you will use often. The
first is signature generation via &lt;code&gt;rbs prototype&lt;&#x2F;code&gt;. The second is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ruby&#x2F;gem_rbs_collection&quot;&gt;RBS Collection&lt;&#x2F;a&gt;, effectively
Bundler for signatures. The RBS Collection community repository holds signatures
for many popular RubyGems, notably Rails and Sidekiq. Your application interacts
with RBS Collection via the &lt;code&gt;rbs collection&lt;&#x2F;code&gt; command.&lt;&#x2F;p&gt;
&lt;p&gt;As mentioned earlier, type-checking is performed by Steep. You will make
frequent use of the Steep CLI to check your app for warnings and errors, but you
can also integrate Steep directly into your text editor thanks to LSP. Here&#x27;s an
example for Helix:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ~&#x2F;.config&#x2F;helix&#x2F;languages.toml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;language-server.steep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;command&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;steep&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;args&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;langserver&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[[&lt;&#x2F;span&gt;&lt;span&gt;language&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;ruby&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;language-servers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;steep&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;ll walk through using each of these tools in a Rails app in the next section.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;adding-rbs-to-a-new-rails-app&quot;&gt;Adding RBS to a new Rails app&lt;&#x2F;h2&gt;
&lt;p&gt;From a clean slate:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rails&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; new myapp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add Steep and run &lt;code&gt;init&lt;&#x2F;code&gt; to generate its configuration file (&lt;code&gt;Steepfile&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;bundle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; add steep&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;bundle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; exec steep init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Open up &lt;code&gt;Steepfile&lt;&#x2F;code&gt; and you&#x27;ll see a bunch of configuration options commented
out. Feel free to keep these around for future reference, but the configuration
that we&#x27;ll use for this post is the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Steepfile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;target &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  signature &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;sig&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  check &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;app&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In short,&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A project may contain many targets, although we only make use of the &lt;code&gt;:app&lt;&#x2F;code&gt;
target. You will likely want a separate target for tests.&lt;&#x2F;li&gt;
&lt;li&gt;Our signature files go in the &lt;code&gt;sig&#x2F;&lt;&#x2F;code&gt; directory.&lt;&#x2F;li&gt;
&lt;li&gt;When type-checking, we check the entirety of &lt;code&gt;app&#x2F;&lt;&#x2F;code&gt;. You can use separate
&lt;code&gt;check&lt;&#x2F;code&gt; lines to incrementally adopt RBS, globbing particular files or
directories. More on this later.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Run the type-checker and you&#x27;ll observe some 13 errors:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;bundle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; exec steep check&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Output of 13-ish errors and warnings...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Most of these errors and warnings are from missing definitions, as we haven&#x27;t
yet added any signature files. However, quite a few of the errors point to
missing definitions in baseline Rails classes (e.g. &lt;code&gt;ActiveRecord&lt;&#x2F;code&gt;). Steep
doesn&#x27;t yet know how to locate signatures for Rails.&lt;&#x2F;p&gt;
&lt;p&gt;Rails doesn&#x27;t maintain its own signatures, but luckily there are
community-maintained signatures in RBS Collection. Let&#x27;s set them up:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; collection init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; collection install&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;During installation, RBS Collection walks your &lt;code&gt;Gemfile.lock&lt;&#x2F;code&gt; and pulls
signatures for gems that (a) are registered in the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ruby&#x2F;gem_rbs_collection&quot;&gt;repository&lt;&#x2F;a&gt; or (b) ship with their
own signatures via RubyGems. After running the initialization commands, you&#x27;ll
have three new files&#x2F;folders on your machine:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rbs_collection.yaml&lt;&#x2F;code&gt; (a configuration file that we won&#x27;t mess with)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rbs_collection.lock.yaml&lt;&#x2F;code&gt; (equivalent to a &lt;code&gt;Gemfile.lock&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;.gem_rbs_collection&#x2F;&lt;&#x2F;code&gt; (the actual signatures pulled during install)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You will likely want to add &lt;code&gt;.gem_rbs_collection&#x2F;&lt;&#x2F;code&gt; to your &lt;code&gt;.gitignore&lt;&#x2F;code&gt;. You
will also want to add &lt;code&gt;rbs collection install&lt;&#x2F;code&gt; to your &lt;code&gt;bin&#x2F;setup&lt;&#x2F;code&gt; to ensure
future users of your Rails app won&#x27;t run into trouble with missing signatures.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# bin&#x2F;setup&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;puts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;== Installing dependencies ==&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;system&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;bundle check&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; system!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;bundle install&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;puts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;== Installing signatures ==&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;system!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;rbs collection install&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run &lt;code&gt;check&lt;&#x2F;code&gt; again and observe that we&#x27;ve fixed all of the errors, though we
still have 5 warnings:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;bundle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; exec steep check&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# 0 errors, 5 warnings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These warnings are from missing signatures in our actual application code. Since
the codebase is virtually empty, the prototype signatures generated by &lt;code&gt;rbs&lt;&#x2F;code&gt; are
more than adequate:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; prototype rb --out-dir=sig&#x2F;app&#x2F; app&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Output:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;Processing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;app&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  Generating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; RBS for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;app&#x2F;controllers&#x2F;application_controller.rb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; Writing RBS to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sig&#x2F;app&#x2F;controllers&#x2F;application_controller.rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  Generating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; RBS for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;app&#x2F;helpers&#x2F;application_helper.rb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; Writing RBS to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sig&#x2F;app&#x2F;helpers&#x2F;application_helper.rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  Generating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; RBS for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;app&#x2F;jobs&#x2F;application_job.rb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; Writing RBS to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sig&#x2F;app&#x2F;jobs&#x2F;application_job.rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  Generating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; RBS for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;app&#x2F;mailers&#x2F;application_mailer.rb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; Writing RBS to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sig&#x2F;app&#x2F;mailers&#x2F;application_mailer.rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  Generating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; RBS for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;app&#x2F;models&#x2F;application_record.rb&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; Writing RBS to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;sig&#x2F;app&#x2F;models&#x2F;application_record.rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Steep doesn&#x27;t actually care where your RBS files are located. Matching
the directory structure of &lt;code&gt;app&#x2F;&lt;&#x2F;code&gt; is merely convention. We&#x27;ll cover a more
complicated directory structure that makes use of &lt;code&gt;rbs subtract&lt;&#x2F;code&gt; later on.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Run &lt;code&gt;check&lt;&#x2F;code&gt; again and observe no errors:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bundle exec steep check&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Type checking files:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;......&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;No type error detected. 🫖&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s most of the setup done.&lt;&#x2F;p&gt;
&lt;p&gt;Things get more complicated with the addition of &lt;code&gt;rbs_rails&lt;&#x2F;code&gt;, which adds another
layer of code generation to our signature folder. I will talk through that setup
later on, after discussing incremental adoption.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;incremental-adoption-for-existing-rails-apps&quot;&gt;Incremental adoption for existing Rails apps&lt;&#x2F;h2&gt;
&lt;p&gt;The nature of RBS signatures living separately from the Ruby code they represent
is convenient for incremental adoption. Same goes for the &lt;code&gt;check&lt;&#x2F;code&gt; attribute in
our &lt;code&gt;Steepfile&lt;&#x2F;code&gt;, which enables us to slowly migrate directories over to
type-checking.&lt;&#x2F;p&gt;
&lt;p&gt;Incremental adoption isn&#x27;t perfect because you&#x27;ll inevitably have warnings for
untyped code, but that can be mitigated by only checking for errors in CI and
keeping an ignore file for known violations.&lt;&#x2F;p&gt;
&lt;p&gt;The general strategy begins with a &lt;code&gt;Steepfile&lt;&#x2F;code&gt; that works against one directory
at a time:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Steepfile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;target &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  signature &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;sig&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  check &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;app&#x2F;models&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  check &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;app&#x2F;services&#x2F;**&#x2F;some-dir&#x2F;*.rb&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # Add more as you develop more signatures...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The workflow looks something like this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Add a new directory to &lt;code&gt;Steepfile&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Prototype the directory&lt;&#x2F;li&gt;
&lt;li&gt;Run &lt;code&gt;steep check&lt;&#x2F;code&gt; and fix violations&lt;&#x2F;li&gt;
&lt;li&gt;Ignore warnings or errors due to untyped references&lt;&#x2F;li&gt;
&lt;li&gt;Check-in your work&lt;&#x2F;li&gt;
&lt;li&gt;Repeat&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I mention ignoring warnings or errors due to untyped references because you&#x27;ll
often come across untyped code when working incrementally. For example, adding
types for a worker that references a model and a service, where the model or
service hasn&#x27;t itself been typed.&lt;&#x2F;p&gt;
&lt;p&gt;To help mitigate this issue, I recommend starting with &lt;code&gt;app&#x2F;models&#x2F;&lt;&#x2F;code&gt;, as it&#x27;s
arguably the directory with the widest surface area in your Rails app.&lt;&#x2F;p&gt;
&lt;p&gt;Another helpful technique is to configure Steep to (a) ignore warnings and (b)
ignore certain known violations. You can automate this configuration with a Rake
task:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Rakefile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;steep&#x2F;rake_task&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Steep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;RakeTask&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;severity_level&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;error&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;with_expectations&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When we run Steep, we&#x27;ll ask it to save known violations into a YAML file so
they can be ignored by future runs if the &lt;code&gt;with_expectations&lt;&#x2F;code&gt; flag is true.
Generate the file of known violations by invoking Steep with a couple of extra
flags:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;bundle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; exec steep --severity-level=error --save-expectations&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After generating known violations, check in the file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; add steep_expectations.yml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From here on out, the Rake task will ignore any violation present in that file.
Use this sparingly!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;generating-signatures-for-rails-models&quot;&gt;Generating signatures for Rails models&lt;&#x2F;h3&gt;
&lt;p&gt;As you&#x27;ll quickly find out, the signatures generated via &lt;code&gt;rbs prototype&lt;&#x2F;code&gt; are
very primitive. This is especially true for Rails models, which make heavy use
of metaprogramming.&lt;&#x2F;p&gt;
&lt;p&gt;Instead of generating model signatures by hand, use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pocke&#x2F;rbs_rails&quot;&gt;rbs_rails&lt;&#x2F;a&gt;. This gem provides a few Rake
tasks that generate ActiveRecord models based on their runtime behavior, filling
in a ton of detail that would otherwise be missing from &lt;code&gt;rbs prototype&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;bundle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; add rbs_rails --require=false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;bin&#x2F;rails&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; g rbs_rails:install&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Installation provides the following tasks in &lt;code&gt;rbs.rake&lt;&#x2F;code&gt; (under the &lt;code&gt;rbs_rails&lt;&#x2F;code&gt;
namespace):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;generate_rbs_for_models&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;generate_rbs_for_path_helpers&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;all&lt;&#x2F;code&gt; (models and path helpers)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When migrating an existing app, your &lt;code&gt;app&#x2F;models&lt;&#x2F;code&gt; directory should probably be
your first priority and &lt;code&gt;rbs_rails:all&lt;&#x2F;code&gt; is a huge help.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;keep-generated-signatures-separate&quot;&gt;Keep generated signatures separate&lt;&#x2F;h4&gt;
&lt;p&gt;Up to this point in the post we&#x27;ve been prototyping signatures directly into our
app&#x27;s &lt;code&gt;sig&#x2F;app&#x2F;&lt;&#x2F;code&gt; folder. However, you do not want to do this with &lt;code&gt;rbs_rails&lt;&#x2F;code&gt;.
In fact, &lt;code&gt;rbs_rails&lt;&#x2F;code&gt; will place all of its generated signatures in
&lt;code&gt;sig&#x2F;rbs_rails&#x2F;&lt;&#x2F;code&gt; by default, and for good reason.&lt;&#x2F;p&gt;
&lt;p&gt;You don&#x27;t want to be in a situation where your hand-written edits are
overwritten by generated code. This is especially relevant with &lt;code&gt;rbs_rails&lt;&#x2F;code&gt;,
because you&#x27;ll be re-running the &lt;code&gt;rbs_rails&lt;&#x2F;code&gt; rake tasks anytime a model changes,
particularly if that change involves database migrations. Unlike the signatures
created by &lt;code&gt;rbs prototype&lt;&#x2F;code&gt;, which serve merely as a starting point, the
signatures generated by &lt;code&gt;rbs_rails&lt;&#x2F;code&gt; can and should be kept separate.&lt;&#x2F;p&gt;
&lt;p&gt;To enable hand-written edits to &lt;code&gt;rbs_rails&lt;&#x2F;code&gt;, we&#x27;ll set up a wrapper task that
merges hand-written signatures with those generated programmatically. This
allows us to reliably re-run the &lt;code&gt;rbs_rails&lt;&#x2F;code&gt; rake tasks without needing to worry
that the new signatures will overwrite custom modifications.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Rakefile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;namespace &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;rbs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  task &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;generate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # Use rbs_rails to generate signatures&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;    Rake&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Task&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;rbs_rails:all&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;].&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;invoke&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # Remove hand-written touch-ups from rbs_rails generated code.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # This assumes you have hand-written signatures in sig&#x2F;app&#x2F;models.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    `bin&#x2F;rbs subtract --write sig&#x2F;rbs_rails sig&#x2F;app&#x2F;models`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The necessary folder structure looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sig&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rbs_rails&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    app&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      models&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        user.rbs  # auto-generated&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  app&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    models&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      user.rbs    # hand-written&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After running &lt;code&gt;rbs:generate&lt;&#x2F;code&gt;, the auto-generated code will have definitions
removed if those definitions conflict with hand-written code. In effect,
hand-written code is always preferred to auto-generated. Both directories are
checked in to source control.&lt;&#x2F;p&gt;
&lt;p&gt;The author of &lt;code&gt;rbs subtract&lt;&#x2F;code&gt; and &lt;code&gt;rbs_rails&lt;&#x2F;code&gt; wrote a design doc where you can
learn more about this workflow:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hackmd.io&#x2F;@pocke&#x2F;rktQ3Vhe3&quot;&gt;Design Doc of rbs subtract&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;signatures-for-third-party-code&quot;&gt;Signatures for third-party code&lt;&#x2F;h3&gt;
&lt;p&gt;Using RBS Collection to manage third-party gems can be a little confusing, so
here are some guidelines.&lt;&#x2F;p&gt;
&lt;p&gt;If a gem is part of RBS Collection (and present in your &lt;code&gt;Gemfile.lock&lt;&#x2F;code&gt;) you&#x27;re
good to go. Run &lt;code&gt;rbs collection init&#x2F;install&lt;&#x2F;code&gt; and reap the benefits of
community-maintained signatures.&lt;&#x2F;p&gt;
&lt;p&gt;If a gem isn&#x27;t in the collection and does not publish its own signatures, you
will need to provide them yourself.&lt;&#x2F;p&gt;
&lt;p&gt;For stdlib gems, things get a little confusing. If that gem is truly part of the
Ruby stdlib, but not present in your &lt;code&gt;Gemfile.lock&lt;&#x2F;code&gt;, you need to tell Steep that
it&#x27;s active in your project. This is the case for gems like &lt;code&gt;yaml&lt;&#x2F;code&gt; or &lt;code&gt;net&#x2F;http&lt;&#x2F;code&gt;
that need to be required despite existing in Ruby Core:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;target &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;app&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # ... snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  library &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;yaml&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  library &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;net-http&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s easy to forget what should and should not be added as a &quot;library&quot; in your
Steepfile, so here&#x27;s my guidance. Never add third-party gems to your Steepfile.
Only add stdlib gems, if that gem is not present in your &lt;code&gt;Gemfile.lock&lt;&#x2F;code&gt;.
Everything else is either already installed via RBS Collection or the signatures
do not exist and you will need to write them yourself.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;code-comments&quot;&gt;Code comments&lt;&#x2F;h3&gt;
&lt;p&gt;By default &lt;code&gt;rbs prototype&lt;&#x2F;code&gt; copies code comments from your Ruby files into your
RBS files. This brings up an interesting question: where should your comments
live, in the code or in the signatures?&lt;&#x2F;p&gt;
&lt;p&gt;The answer is both!&lt;&#x2F;p&gt;
&lt;p&gt;In RBS files, add comments that explain high-level details about the API. These
comments should describe the intended purpose of the class and help developers
understand how to use it.&lt;&#x2F;p&gt;
&lt;p&gt;In Ruby files, add comments that explain the intricacies of the code. These
comments are made for those modifying the code to suit new purposes (or fix
existing bugs).&lt;&#x2F;p&gt;
&lt;p&gt;For example,&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# feed_parser.rbs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Fetch and RSS&#x2F;Atom feed and return a list of Articles.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; FeedParser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # @param url [String] The URL of the RSS or Atom feed.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; initialize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; void&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # Fetches and parses the feed, returning a list of articles.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # @return [Array[Article]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; articles&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Array&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Article&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# feed_parser.rb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; FeedParser&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; articles&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # Note that RSS and Atom differ in the following respects: ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;rbs-will-change-the-way-you-write-ruby-code&quot;&gt;RBS will change the way you write Ruby code&lt;&#x2F;h3&gt;
&lt;p&gt;I have found that, like TypeScript, RBS changes the way you write Ruby code. I
generally think this is good news for the clarity of your code.&lt;&#x2F;p&gt;
&lt;p&gt;The most common case that I&#x27;ve run into is the issue of &quot;flow specificity&quot;, or,
the inability of Steep to narrow a type based on context. Do not take this as a
bad mark against Steep, TypeScript suffers from the same issue. The problem is
that Ruby code carries certain patterns that are more prone to flow specificity
issues because of how Ruby developers like to write code.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; FooService&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; execute&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return unless&lt;&#x2F;span&gt;&lt;span&gt; some_variable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    do_something_with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;some_variable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  private&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; do_something_with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; some_variable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Say that &lt;code&gt;some_variable&lt;&#x2F;code&gt; is something that could be &lt;code&gt;nil&lt;&#x2F;code&gt;. Here&#x27;s the signature:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; FooService&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  @some_variable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; Integer&lt;&#x2F;span&gt;&lt;span&gt;?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; execute&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; void&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  private&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; do_something_with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; void&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; some_variable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; Integer&lt;&#x2F;span&gt;&lt;span&gt;?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Run Steep and you&#x27;ll see the following error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;app&#x2F;services&#x2F;foo_service.rb:5:22:&lt;&#x2F;span&gt;&lt;span&gt; [error] Cannot pass a value of type &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:Integer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; an argument of type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; `&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:Integer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;│&lt;&#x2F;span&gt;&lt;span&gt;   (::Integer &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;: ::Integer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;│&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;     nil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;: ::Integer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;│&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;│&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; Diagnostic ID: Ruby::ArgumentTypeMismatch&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;│&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;└&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;     do_something_with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;some_variable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;                        ~~~~~~~~~~~~~&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;Detected&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; problem from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; file&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The problem is that although we check for the existence of &lt;code&gt;some_variable&lt;&#x2F;code&gt; at
the beginning of &lt;code&gt;execute&lt;&#x2F;code&gt;, &lt;code&gt;some_variable&lt;&#x2F;code&gt; is a method. Steep cannot know that
the second call of &lt;code&gt;some_variable&lt;&#x2F;code&gt; takes place after the first call was
validated, nor that the second call is guaranteed to be non-nil due to flow
control patterns.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the workaround:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; FooService&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; execute&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; some_variable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return unless&lt;&#x2F;span&gt;&lt;span&gt; value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    do_something_with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Assigning the method to a variable, checking that variable, and continuing to
use that checked variable will lead Steep to the correct conclusion.&lt;&#x2F;p&gt;
&lt;p&gt;Now although I mentioned that this exact issue also exists in TypeScript, I&#x27;ve
never seen it in practice. I don&#x27;t think JS developers are incentivized to write
flow control in the Ruby way because JS doesn&#x27;t have the same optional
parentheses that mask whether or not an identifier is a method or a variable
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.typescriptlang.org&#x2F;play&#x2F;?#code&#x2F;MYewdgzgLgBBIFsCmA1AhgJwJZoEYBskYBeGACgEoAuGMAVwVyQxgB9a798SA+GAFgBMAKGGhIsACYgAyoiRQAFljABzEuTA16jZhV4wA3gF9RWAGbl4ydNjyFK+w8Jgxpc5EpWqy11JhwCJEdhYyA&quot;&gt;playground link&lt;&#x2F;a&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; someVariable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-style: italic;&quot;&gt; null&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 42&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; doSomething&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Looks weird and TypeScript complains!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;someVariable&lt;&#x2F;span&gt;&lt;span&gt;())&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  doSomething&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;someVariable&lt;&#x2F;span&gt;&lt;span&gt;())&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; &#x2F;&#x2F; ArgumentError&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a contrived example, but it&#x27;s a problem that I&#x27;ve seen numerous times in
a recently-converted Rails codebase. Especially when dealing with associations
that allow &lt;code&gt;null&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The solution, I think, is to avoid writing Ruby code that proliferates &lt;code&gt;nil&lt;&#x2F;code&gt;
values. That means restructuring code with fewer methods and more local
variables, that also means longer methods that pack more content, which finally
means fewer one-line methods.&lt;&#x2F;p&gt;
&lt;p&gt;Clean Code aesthetics are on their way out, this is one more nail in the coffin.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Adding types to an existing Rails app is not trivial, but RBS allows for
incremental adoption.&lt;&#x2F;p&gt;
&lt;p&gt;After working with RBS in a production application for two weeks, my team has
already discovered a number of bugs that we&#x27;ve identified as real issues
matching our Airbrake exception logs. RBS clearly revealed the bug, often a
&lt;code&gt;NoMethodError&lt;&#x2F;code&gt; pointing to an object that might be &lt;code&gt;nil&lt;&#x2F;code&gt;, and identified other
potential failure points that did not happen to align with our primary execution
path.&lt;&#x2F;p&gt;
&lt;p&gt;Resolving the bug in these circumstances wasn&#x27;t always trivial due to the
tendency for Rubyists to wrap optional states behind methods that sidestep
Steep&#x27;s type-checking. Rewriting classes to suit the signatures and eliminate
&lt;code&gt;nil&lt;&#x2F;code&gt; values, however, has been a net positive.&lt;&#x2F;p&gt;
&lt;p&gt;Overall, RBS has been well-worth the investment.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Deno&#x27;s stdlib is Available Outside Deno</title>
        <published>2025-09-17T00:00:00+00:00</published>
        <updated>2025-09-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-09-17-deno-stdlib-for-all/"/>
        <id>https://mgmarlow.com/words/2025-09-17-deno-stdlib-for-all/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-09-17-deno-stdlib-for-all/">&lt;p&gt;Out of the craziness that has been the last couple weeks of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=45260741&quot;&gt;NPM supply-chain drama&lt;&#x2F;a&gt; we&#x27;re
reminded that Deno at least had a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;deno.com&#x2F;blog&#x2F;a-more-secure-npm&quot;&gt;few things right&lt;&#x2F;a&gt; when it pushed for a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.deno.com&#x2F;runtime&#x2F;fundamentals&#x2F;security&#x2F;&quot;&gt;secure-by-default&lt;&#x2F;a&gt; stance
towards third-party code. In the same general space we&#x27;ve seen
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;pnpm.io&#x2F;&quot;&gt;Pnpm&lt;&#x2F;a&gt;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;socket.dev&#x2F;blog&#x2F;pnpm-10-0-0-blocks-lifecycle-scripts-by-default&quot;&gt;disable postinstall scripts by default&lt;&#x2F;a&gt;
and more recently release some
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pnpm&#x2F;pnpm&#x2F;releases&#x2F;tag&#x2F;v10.16.0&quot;&gt;minor mitigating configuration settings&lt;&#x2F;a&gt;.
It is certainly an interesting time to be a JavaScript developer, not that this
kind of issue is unique to NPM.&lt;&#x2F;p&gt;
&lt;p&gt;There are, however, many reasons why NPM is the perfect target for this kind of
attack. JavaScript is a language with a small standard library, an enormous user
base, and a general social acceptance of insane &lt;code&gt;node_modules&lt;&#x2F;code&gt; folders. We all
remember the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Npm_left-pad_incident&quot;&gt;left-pad incident&lt;&#x2F;a&gt;, a mere
17 lines of code that sent the JavaScript ecosystem into a spiral.&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, this TIL isn&#x27;t about all of that. It&#x27;s about one of the nice things I
stumbled upon while reading the aforementioned Hacker News thread: Deno&#x27;s
standard library is widely available even for non-Deno users. You can find it on
Deno&#x27;s independent module registry, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jsr.io&#x2F;@std&quot;&gt;JSR&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When I work in JavaScript or Rust I&#x27;m always envious of Go. I may hate the
syntax and find the code lacking expressivity, but it undeniably has a rich and
simple standard library. I&#x27;ve often seen an ethos in Go to stick to &quot;pure Go&quot;
and avoid pulling in third-party implementations for items already covered by
the stdlib. It&#x27;s a stacked comparison because JavaScript was never meant to be a
language running on the server (that&#x27;s really a Node problem, after all), but
times have changed and JavaScript is no longer just a browser runtime.&lt;&#x2F;p&gt;
&lt;p&gt;The ability to use Deno&#x27;s standard library for non-Deno projects offers a nice
default for JavaScript developers. We might just be trading one dependency for
another, but I have a high degree of trust in the Deno folks to make good on
their architectural goals and offer a widely useful, yet dependency-free set of
functions.&lt;&#x2F;p&gt;
&lt;p&gt;On a quick scan of Deno&#x27;s &lt;code&gt;@std&lt;&#x2F;code&gt; modules, there are quite a few goodies:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@std&#x2F;collections&lt;&#x2F;code&gt; which covers utility functions a la lodash&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;@std&#x2F;uuid&lt;&#x2F;code&gt; for UUID generation&#x2F;validation&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;@std&#x2F;async&lt;&#x2F;code&gt; for quite a few commonly-implemented async handlers (debounce,
retry, exponential back-off)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;@std&#x2F;csv&lt;&#x2F;code&gt; and &lt;code&gt;@std&#x2F;yaml&lt;&#x2F;code&gt; for common file formats&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The &lt;code&gt;retry&lt;&#x2F;code&gt; function from &lt;code&gt;@std&#x2F;async&lt;&#x2F;code&gt; looks especially appealing:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; retry&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;@std&#x2F;async&#x2F;retry&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; req&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; async&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; some function that throws sometimes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Below resolves to the first non-error result of `req`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; retryPromise&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; retry&lt;&#x2F;span&gt;&lt;span&gt;(req&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  multiplier&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  maxTimeout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 60000&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  maxAttempts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  minTimeout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  jitter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Something that&#x27;s definitely worth exploring for new projects. The source code
for the entirety of Deno&#x27;s standard library is available here:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;denoland&#x2F;std&quot;&gt;denoland&#x2F;std&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>August Was a Great Month for Puzzles</title>
        <published>2025-09-04T00:00:00+00:00</published>
        <updated>2025-09-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-09-04-august-puzzlers/"/>
        <id>https://mgmarlow.com/words/2025-09-04-august-puzzlers/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-09-04-august-puzzlers/">&lt;p&gt;I can&#x27;t stop thinking about two puzzle games that came out last month. They&#x27;re
both the kind of game that makes me jealous for not having designed it myself,
then jealous again for knowing that even with the idea in my hands I couldn&#x27;t
execute it as well. Those two games are
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;2702170&#x2F;Strange_Jigsaws&#x2F;&quot;&gt;Strange Jigsaws&lt;&#x2F;a&gt;
and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;2721890&#x2F;oo&#x2F;&quot;&gt;Öoo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;strange-jigsaws.jpg&quot; alt=&quot;Strange Jigsaws screenshot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Strange Jigsaws is a meta-puzzle exploration built on the humble jigsaw puzzle.
If you&#x27;re not into jigsaws (I don&#x27;t blame you, they&#x27;re not particularly
&lt;em&gt;puzzling&lt;&#x2F;em&gt;) don&#x27;t be dissuaded. The puzzles in Strange Jigsaws encompass a wide
variety of different logic and themed puzzles, jigsaws only in the narrowest
sense.&lt;&#x2F;p&gt;
&lt;p&gt;Thanks to those jigsaws, the game is incredibly tactile. FLEB clearly knows what
he&#x27;s doing when he designs puzzles that emphasize juicy interactions. Moving,
rotating, and slotting puzzle pieces is dopamine delivered straight to the
brain.&lt;&#x2F;p&gt;
&lt;p&gt;The real triumph of Strange Jigsaws is the breadth of ideas and overall quality
of their execution. There are a &lt;em&gt;ton&lt;&#x2F;em&gt; of different puzzle ideas in the game,
almost none of which are a plain ol&#x27; jigsaw. Each puzzle is a fresh challenge
with a consistent difficulty, a bit on the easy side but only so much as to
avoid any sense of frustration.&lt;&#x2F;p&gt;
&lt;p&gt;I played through Strange Jigsaws in three, one-hour sessions. It&#x27;s a game that&#x27;s
very amenable to short sessions since each puzzle is neatly self-contained.&lt;&#x2F;p&gt;
&lt;p&gt;Öoo is likewise a short game. I finished in just under two hours, but that two
hours was a single, non-stop playthrough. Öoo is not an easy game to put down.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;ooo.jpg&quot; alt=&quot;Öoo screenshot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Design-wise, Öoo is kind of the antithesis of Strange Jigsaws. Where Strange
Jigsaws delights by introducing new mechanics with every puzzle, Öoo is a study
into the possibility space of a single idea. That single idea grows deeper as
the player accumulates the knowledge of how to apply it to the surrounding
world.&lt;&#x2F;p&gt;
&lt;p&gt;That means mechanics in Öoo aren&#x27;t so much introduced as revealed. Every puzzle
advances the player&#x27;s intuition of the mechanical language underlying Öoo,
unveiling how that language interacts with the surrounding world and how the
player can use that language to solve puzzles. The player character doesn&#x27;t gain
new abilities. Instead, the player learns the world and the world reveals itself
as one great puzzle.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s hard to overstate how much design excellence Öoo squeezes out of its
mechanics. Several times a solution left me blurting out in laughter, amazed by
how Öoo bent the rules of the world and re-contextualized my expectations. It&#x27;s
a uniquely joyful experience.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s hard to believe that two of the best puzzle games that I&#x27;ve played in the
last two years came out in the same month. Do yourself a favor and check them
out.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>New Emacs Package: Helix Mode</title>
        <published>2025-05-17T00:00:00+00:00</published>
        <updated>2025-05-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-05-17-helix-emacs-package/"/>
        <id>https://mgmarlow.com/words/2025-05-17-helix-emacs-package/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-05-17-helix-emacs-package/">&lt;p&gt;Short version:&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m building a new Emacs package:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;helix-mode&quot;&gt;Helix Mode&lt;&#x2F;a&gt;. Helix Mode implements the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.helix-editor.com&#x2F;from-vim.html&quot;&gt;Helix modal keybindings&lt;&#x2F;a&gt; in Emacs.
It&#x27;s been my daily driver for about a month, and while it still has some bugs,
I&#x27;m reasonably confident it&#x27;s in a usable state.&lt;&#x2F;p&gt;
&lt;p&gt;Install Helix Mode in Emacs 30.1:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; helix&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  :vc (:url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;helix-mode&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  :config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (helix-mode))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Long version:&lt;&#x2F;p&gt;
&lt;p&gt;About six months ago I attempted to set up Emacs on a Windows machine and found
it to be an immensely frustrating experience. The default Windows Emacs build
works well enough if you don&#x27;t use any third-party packages, but who is using
Emacs who isn&#x27;t also using packages? Diving into the complexity of setting up my
entire Emacs config exposed a reliance on Linux CLI tools that I hadn&#x27;t
installed, and attempting to configure my Windows environment to properly export
paths with cygwin&#x2F;w64devkit&#x2F;whatever was not going well.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually I gave up and swapped over to WSL, the Linux emulation layer for
Windows. For the most part WSL is great, provided you use the terminal.
Attempting to use GUI Emacs from WSL results in a Frankenstein-like windowing
experience. It kinda works but is far from ideal.&lt;&#x2F;p&gt;
&lt;p&gt;With these frustrations top of mind, I decided to drop Emacs altogether and
experiment with a terminal-first workflow. I had already been itching for an
excuse to try out &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;helix-editor.com&quot;&gt;Helix&lt;&#x2F;a&gt; and this felt like the
perfect opportunity.&lt;&#x2F;p&gt;
&lt;p&gt;As it turns out, Helix is an incredibly capable text editor, if a bit light on
the tooling. The vim-ish keybinding scheme is magical once you understand the
basics, and the automatic configuration settings for tree-sitter and LSP work
amazingly well. That said, Helix is not very featureful and expects a lot of
supplemental work done in the terminal. It really needs to be paired with a
terminal multiplexer like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;zellij-org&#x2F;zellij&quot;&gt;Zellij&lt;&#x2F;a&gt; or
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tmux&#x2F;tmux&#x2F;wiki&quot;&gt;tmux&lt;&#x2F;a&gt; to work effectively.&lt;&#x2F;p&gt;
&lt;p&gt;I settled on tmux and set up a light config that emulates Emacs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# remap prefix from C-b to C-x&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unbind C-b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;set-option -g prefix C-x&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind-key C-x send-prefix&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# split panes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind 0 kill-pane&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind 1 kill-pane -a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind 2 split-window -v&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind 3 split-window -h&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unbind &amp;#39;&amp;quot;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unbind %&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# zellij-style pane swapping&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind h select-pane -L&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind j select-pane -D&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind k select-pane -U&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind l select-pane -R&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you&#x27;re willing to settle for a minimal text editor that&#x27;s supplemented with
tmux and small scripting languages, I think Helix is incredibly compelling. It
remixes the Vim keybindings&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; in a way that makes them far more intuitive.&lt;&#x2F;p&gt;
&lt;p&gt;The principle change is flipping around Vim&#x27;s verb-object model. In Vim, if you
want to delete the next word, you first press &lt;code&gt;d&lt;&#x2F;code&gt; (delete) and followup with &lt;code&gt;w&lt;&#x2F;code&gt;
(word). The idea is that you declare your action before your intended target,
queuing up what you intend to perform before telling Vim what to perform it on.&lt;&#x2F;p&gt;
&lt;p&gt;Helix is the opposite. First you select your intended target: &lt;code&gt;w&lt;&#x2F;code&gt; (word). Helix
automatically selects the word as the cursor navigates to it, clarifying the
selection visually. Then you perform your action: &lt;code&gt;d&lt;&#x2F;code&gt; (delete).&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s kind of like Helix is operating in a permanent, automatic visual mode. In
Vim, I often found myself resorting to visual mode because I didn&#x27;t inherently
trust my muscle memory to select the appropriate selection before performing a
deletion. This is problematic because Vim&#x27;s visual mode makes everything less
efficient. Here&#x27;s how you&#x27;d delete with visual mode:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Press &lt;code&gt;v&lt;&#x2F;code&gt; to enter visual mode.&lt;&#x2F;li&gt;
&lt;li&gt;Press &lt;code&gt;w&lt;&#x2F;code&gt; to navigate word.&lt;&#x2F;li&gt;
&lt;li&gt;Press &lt;code&gt;d&lt;&#x2F;code&gt; to delete.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The funny thing is that visual mode makes Vim function like Helix, but requires
an extra keypress for every action. In Helix, the selection is automatic so you
don&#x27;t
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;vim&#x2F;comments&#x2F;1ev7o9k&#x2F;you_might_be_overusing_vim_visual_mode&#x2F;&quot;&gt;lose any street cred&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;back-to-emacs&quot;&gt;Back to Emacs&lt;&#x2F;h2&gt;
&lt;p&gt;Despite enjoying the Helix + tmux workflow, in the last couple months I&#x27;ve come
to miss some of the niceties of Emacs:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Built-in diffing tools like &lt;code&gt;vc-diff&lt;&#x2F;code&gt; are really nice, even if I prefer the
git CLI for most everything else.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;project.el&lt;&#x2F;code&gt; is unbeatable. Helix doesn&#x27;t have a concept of workspaces, nor
does it allow global search &amp;amp; replace like &lt;code&gt;project-query-replace-regexp&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Helix only recently got a proper file navigator but it hasn&#x27;t yet been
released. I doubt that it will be as useful as dired.&lt;&#x2F;li&gt;
&lt;li&gt;Emacs remains the king of Lisp editing (shoutout to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;itch.io&#x2F;jam&#x2F;spring-lisp-game-jam-2025&quot;&gt;2025 Spring Lisp Game Jam&lt;&#x2F;a&gt;
where I&#x27;m using Fennel &amp;amp; Love2d).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And so the idea for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;helix-mode&quot;&gt;Helix Mode&lt;&#x2F;a&gt;
developed. It&#x27;s easily my most ambitious Emacs package, both in lines of code
and functionality. But it brings all of my favorite pieces of Helix into the
Emacs editing experience.&lt;&#x2F;p&gt;
&lt;p&gt;Helix Mode isn&#x27;t designed to re-implement all of Helix, nor provide the
extensibility of the venerable Evil Mode. Instead it&#x27;s aimed at the subset of
Helix keybindings responsible for editing and navigation, leaving everything
else to the responsibility of Emacs. That means you&#x27;ll still be using stuff like
&lt;code&gt;M-x replace-string&lt;&#x2F;code&gt; or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;minad&#x2F;consult&quot;&gt;consult&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;What it does offer is the same object-verb navigation as Helix, complete with
automatic selections. It also includes some of the Helix sub-modes, like the
Goto mode that provides go-to definition (&lt;code&gt;g d&lt;&#x2F;code&gt;) or the Space mode that allows
navigation across a project (&lt;code&gt;SPC f&lt;&#x2F;code&gt;). Both of which integrate with &lt;code&gt;project.el&lt;&#x2F;code&gt;
and &lt;code&gt;xref&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If I haven&#x27;t bored you with the details of my text-editor dabblings over the
past six months, I encourage you to check out
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;helix-mode&quot;&gt;Helix Mode&lt;&#x2F;a&gt;. I have a long list of
features and improvements that I&#x27;d like to make before the 1.0.0 release, but I
think it&#x27;s currently in a very usable state.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Noteworthy that the object-verb idea isn&#x27;t Helix&#x27;s innovation, but
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kakoune.org&quot;&gt;Kakoune&#x27;s&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Action Cable + React</title>
        <published>2025-05-02T00:00:00+00:00</published>
        <updated>2025-05-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-05-02-action-cable-react/"/>
        <id>https://mgmarlow.com/words/2025-05-02-action-cable-react/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-05-02-action-cable-react/">&lt;p&gt;Using Action Cable in React is surprisingly difficult. Every third-party package
that I&#x27;ve come across that offers a custom hook makes some fundamental
assumptions that only hold for simple applications. The vast majority of
tutorials gloss over anything beyond the &quot;log when event is received&quot;
boilerplate.&lt;&#x2F;p&gt;
&lt;p&gt;The principle of integrating Action Cable is easy, since it follows a well-known
subscribe&#x2F;unsubscribe pattern with a &lt;code&gt;useEffect&lt;&#x2F;code&gt;. It looks something like
this&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; createConsume&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;@rails&#x2F;actioncable&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; MyComponent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useMemo&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createConsumer&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;ws:&#x2F;&#x2F;localhost:3000&#x2F;cable&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    []&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;subscriptions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;AlertChannel&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      received&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(data)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;unsubscribe&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  },&lt;&#x2F;span&gt;&lt;span&gt; [consumer])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When all the React application is doing is logging some data, sure, easy-peasy.
But when that component needs to access component state? Now we have a problem.&lt;&#x2F;p&gt;
&lt;p&gt;Let me demonstrate by introducing a stateful counter. It&#x27;s a contrived example,
but it gets the point across that accessing component state is probably useful
for Websocket subscribers.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; MyComponent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; setCount&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useMemo&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createConsumer&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;ws:&#x2F;&#x2F;localhost:3000&#x2F;cable&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    []&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;subscriptions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;AlertChannel&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      received&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(count)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;unsubscribe&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  },&lt;&#x2F;span&gt;&lt;span&gt; [consumer])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; onClick&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; setCount&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;increment&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;button&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This example demonstrates the most obvious flaw: &lt;code&gt;count&lt;&#x2F;code&gt; is missing in the
&lt;code&gt;useEffect&lt;&#x2F;code&gt; dependencies, so no matter how many times the increment button is
clicked, the value logged will always be 0. We have to make the subscription
event handler aware that the count has changed by adding it as a dependency to
the effect.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;subscriptions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;AlertChannel&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    received&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(count)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;unsubscribe&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;},&lt;&#x2F;span&gt;&lt;span&gt; [consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; count])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, theoretically this resolves our counter issue. And, for the most part, it
does. When we receive an Action Cable event from our server, the &lt;code&gt;received&lt;&#x2F;code&gt;
handler logs with the correct value of &lt;code&gt;count&lt;&#x2F;code&gt;. However, in practice this code
has another bug: subscriptions are not properly cleaned up, so the client
responds to the Action Cable message many more times than expected. In my
testing, if I clicked the button 12 times quickly, I would see 6 console logs
when the Action Cable event is broadcast.&lt;&#x2F;p&gt;
&lt;p&gt;It seems that Action Cable is not particularly good about cleaning up
subscriptions that have the same channel key. That is, when the increment button
is clicked multiple times in succession (representing many state updates in our
component), Action Cable does not do a good job ensuring that every connection
is appropriately unsubscribed between renders. You will actually observe errors
in the Rails console, indicating that it’s struggling to keep up:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Could not execute command from ({&amp;quot;command&amp;quot; =&amp;gt; &amp;quot;unsubscribe&amp;quot;, &amp;quot;identifier&amp;quot; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;{\&amp;quot;channel\&amp;quot;:\&amp;quot;AlertChannel\&amp;quot;}&amp;quot;}) [RuntimeError - Unable to find subscription&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;with identifier: {&amp;quot;channel&amp;quot;:&amp;quot;AlertChannel&amp;quot;}]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Digging into the Action Cable source code, it&#x27;s made apparent that the Action
Cable library uses a JSON-stringified representation of the channel name as an
identifier when storing the subscriber. Here&#x27;s the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rails&#x2F;rails&#x2F;blob&#x2F;main&#x2F;actioncable&#x2F;app&#x2F;javascript&#x2F;action_cable&#x2F;subscription.js&quot;&gt;relevant code&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Subscription&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  constructor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; params&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {},&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; mixin&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; consumer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;identifier&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; JSON&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;stringify&lt;&#x2F;span&gt;&lt;span&gt;(params)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    extend&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; mixin)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I can only guess that there&#x27;s a race condition somewhere in Action Cable
involving identical subscription identifiers. I managed to locate a GitHub issue
that tracks a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rails&#x2F;rails&#x2F;issues&#x2F;44652&quot;&gt;similar problem&lt;&#x2F;a&gt; and
lends a little extra support to the theory.&lt;&#x2F;p&gt;
&lt;p&gt;One way to resolve this race condition is to simply include the count in the
channel identifier, even if it&#x27;s unused by the channel on the server. That way a
unique identifier is created for every re-render caused by count:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;subscriptions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;span&gt; channel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;AlertChannel&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      received&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(count)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;unsubscribe&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;},&lt;&#x2F;span&gt;&lt;span&gt; [consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; count])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This seems to get the job done. Each Websocket subscriber is given a unique key
that can be easily located by the Action Cable library for removal. Note that
this only works for serializable data.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#x27;ll note that I also tried passing a reference (via &lt;code&gt;useRef&lt;&#x2F;code&gt;) for the Action
Cable callbacks, hoping that a stable object reference might avoid the need
for the extra &lt;code&gt;useEffect&lt;&#x2F;code&gt; dependency. However, when the Action Cable JS
library creates new subscriptions, it creates an entirely new object,
rendering the stable reference moot.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Anyway, all this to say: be careful when creating Action Cable subscriptions
that rely on external state. Subscriptions created with the same key will likely
not be cleaned up correctly.&lt;&#x2F;p&gt;
&lt;p&gt;Most of the time in React applications this doesn&#x27;t matter that much, since we
can get by with a stable, memoized reference. &lt;code&gt;useQueryClient&lt;&#x2F;code&gt; from
tanstack-query is a great example, since it allows us to invalidate our client
requests when an event is broadcast from the server:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; e.g. from react-query or tanstack-query&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; queryClient&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useQueryClient&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useMemo&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createConsumer&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;ws:&#x2F;&#x2F;localhost:3000&#x2F;cable&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; [])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; || !&lt;&#x2F;span&gt;&lt;span&gt;queryClient)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;subscriptions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;AlertChannel&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    received&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      queryClient&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;invalidateQueries&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        queryKey&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;alerts&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;unsubscribe&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;},&lt;&#x2F;span&gt;&lt;span&gt; [consumer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; queryClient])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For other purposes, it&#x27;s likely a good idea to pass serializable data to the
Websocket channel parameters.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Note that I&#x27;m being careful to only create one consumer for a given
component to avoid re-establishing a connection to the Websocket on every
re-render. It&#x27;s also likely that your consumer will need to live in a
separate hook for authorization purposes.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Kafka on the Shore is My Favorite Murakami Novel</title>
        <published>2025-04-22T00:00:00+00:00</published>
        <updated>2025-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-04-23-kafka-on-the-shore-is-my-favorite-murakami-novel/"/>
        <id>https://mgmarlow.com/words/2025-04-23-kafka-on-the-shore-is-my-favorite-murakami-novel/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-04-23-kafka-on-the-shore-is-my-favorite-murakami-novel/">&lt;p&gt;&lt;em&gt;The following is an email I sent a friend regarding Kafka on the Shore.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve had a week or two now to digest &lt;em&gt;Kafka on the Shore&lt;&#x2F;em&gt; and put some thoughts
together. It&#x27;s definitely my favorite Murakami novel thus far. By a long shot.
The symbolism feels attainable, yet abstract enough that there&#x27;s still room for
reader interpretation. The plot is interesting enough to give weight to the
characters, aided by the dual narrative between Kafka and Nakata&#x2F;Hoshino. It&#x27;s
great.&lt;&#x2F;p&gt;
&lt;p&gt;A couple of ideas stand out to me:&lt;&#x2F;p&gt;
&lt;p&gt;The Oedipus prophecy set upon Kafka isn&#x27;t necessarily that he literally needs to
fulfill the Oedipus contract, but that he needs to carry on the spirit of his
father&#x27;s art. The subtext that I&#x27;m picking up is that his father (the
cat-murdering, flute-blowing madman sculptor) sacrificed everything for his art,
including his relationship with his son. The prophecy that he laid upon Kafka is
his own desire for immortality, extending his name and art with Kafka as the
vehicle. Thus Kafka feels overwhelming pressure and the impossibility of his own
individuality, thus he runs away.&lt;&#x2F;p&gt;
&lt;p&gt;In Miss Saeki, Kafka finds a companion in grief. The two struggle with existing
in the real world, caught instead between the threshold of life and death where
her 15 year old spirit inhabits memories of the past. To her the past and
present are inseparable, the youth that once drove her to compose &lt;em&gt;Kafka on the
Shore&lt;&#x2F;em&gt; has long since vanished.&lt;&#x2F;p&gt;
&lt;p&gt;When Kafka ventures into the forest behind the cabin, he grapples with the idea
of suicide. He&#x27;s literally on the precipice of death, peering into the world
beyond and the purgatory in-between. Here there&#x27;s comfort in routine, at the
cost of the literal music of life. Back home there&#x27;s grief and sadness, but also
the ability to form new memories shaped from the past.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll leave you with one of my favorite quotes near the end of the book,&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Every one of us is losing something precious to us,” he says after the phone
stops ringing. “Lost opportunities, lost possibilities, feelings we can never
get back again. That’s part of what it means to be alive. But inside our
heads—at least that’s where I imagine it—there’s a little room where we store
those memories. A room like the stacks in this library. And to understand the
workings of our own heart we have to keep on making new reference cards. We
have to dust things off every once in a while, let in fresh air, change the
water in the flower vases. In other words, you’ll live forever in your own
private library.”&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How I Organize Email with HEY</title>
        <published>2025-04-18T00:00:00+00:00</published>
        <updated>2025-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-04-18-how-i-use-hey/"/>
        <id>https://mgmarlow.com/words/2025-04-18-how-i-use-hey/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-04-18-how-i-use-hey/">&lt;p&gt;Six months after
&lt;a href=&quot;&#x2F;words&#x2F;2024-10-05-hey-to-fastmail-back-again&#x2F;&quot;&gt;swapping back over to HEY&lt;&#x2F;a&gt; for
email feels like the appropriate time to check in on how it’s going. Here are
the ways I use HEY to organize my email; what works and what doesn&#x27;t.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-workflow&quot;&gt;My workflow&lt;&#x2F;h2&gt;
&lt;p&gt;I read every email that finds its way into my inbox. I hate unread emails, and I
especially hate the # unread counter that most other email platforms surface
within their tab titles. It unnerves me to an unhealthy degree.&lt;&#x2F;p&gt;
&lt;p&gt;That doesn&#x27;t mean that I categorize every email into a special folder or label
to get it out of my inbox. HEY doesn&#x27;t even support this workflow, it lacks the
notion of folders. Instead, read emails that I don&#x27;t immediately delete simply
pile up in the inbox and are
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;cover-art-gallery&#x2F;&quot;&gt;covered with an image&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;HEY claims that their email client is &quot;countless&quot;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, in that there are no
numbers telling you how many emails are in your inbox or how far you&#x27;re behind
in your organizational duties. And for the most part, that&#x27;s true, except for
one glaring counter that tells you how many unscreened emails are awaiting your
approval:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;hey-screener.png&quot; alt=&quot;HEY Screener counter&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Not exactly &quot;countless&quot; but at least the screener is only relevant for emails
from unrecognized senders.&lt;&#x2F;p&gt;
&lt;p&gt;Back on the topic of emails flowing into my inbox, most transactional emails
find their way into the Paper Trail automatically. Receipts of this kind are
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;bundles&#x2F;&quot;&gt;bundled up&lt;&#x2F;a&gt; and kept out of sight, out
of mind.&lt;&#x2F;p&gt;
&lt;p&gt;Other emails that I want to draw temporary importance to reside in one of the
two inbox drawers, Set Aside or Reply Later. I use Set Aside for shipping
notifications, reservations, and other emails that are only relevant for a short
period of time. Reply Later is self-evident. The system is very simple and works
the way HEY intends.&lt;&#x2F;p&gt;
&lt;p&gt;My favorite HEY feature is easily
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;the-feed&#x2F;&quot;&gt;The Feed&lt;&#x2F;a&gt;, which aggregates newsletters
into a single page. In a world where Substack has convinced every blogger that
newsletters are the correct way to distribute their thoughts, The Feed is a
great platform for aggregation. Shout-out to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;javascriptweekly.com&quot;&gt;JavaScript Weekly&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rubyweekly.com&quot;&gt;Ruby Weekly&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The Feed, Paper Trail, Set Aside, and Reply Later make up the bulk of my daily
workflow in HEY. I&#x27;m very happy with these tools and while they are largely
achievable via application of filters, labels, and rules in other inbox systems,
I find the experience in HEY to be an improvement thanks to its email client and
UI.&lt;&#x2F;p&gt;
&lt;p&gt;A few other HEY tools fit into more niche use-cases.&lt;&#x2F;p&gt;
&lt;p&gt;Collections are essentially threads of threads. They&#x27;re similar to labels, but
have the added benefit of aggregating attachments to the top of the page. I tend
to use them for travel plans because they provide easy access to boarding passes
or receipts.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;collections-hero.webp&quot; alt=&quot;HEY Collections&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On the topic of travel, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;clips-highlights&#x2F;&quot;&gt;Clips&lt;&#x2F;a&gt;
are amazing for Airbnb door codes, addresses, or other key information that
often finds itself buried in email marketing fluff. Instead of keeping the email
in the Set Aside drawer and digging into it every time you need to retrieve a
bit of information, simply highlight the relevant text and save it to a Clip.&lt;&#x2F;p&gt;
&lt;p&gt;HEY for domains, while severely limited in its lack of support for multiple
custom domains, at least allows for email extensions. I use
&lt;code&gt;reimburse@mydomain.com&lt;&#x2F;code&gt; to automatically tag incoming email with the
&quot;reimburse&quot; label so I can later retrieve it for my company&#x27;s reimbursement
systems.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;important-missing-features&quot;&gt;Important missing features&lt;&#x2F;h2&gt;
&lt;p&gt;HEY is missing a couple of crucial features that I replace with free
alternatives.&lt;&#x2F;p&gt;
&lt;p&gt;The first is allowing multiple custom domains, a feature of Fastmail that I
dearly miss. I have a few side projects that live on separate domains and I
would prefer those projects to have email contacts matching said domain. If I
wanted to achieve this with HEY, I&#x27;d have to pay an additional $12&#x2F;mo per domain
which is prohibitively expensive&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Instead of creating multiple HEY accounts for multiple domains, I use email
forwarding to point my other custom domains towards my single HEY account.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;forwardemail.net&#x2F;en&quot;&gt;Forward Email&lt;&#x2F;a&gt; is one such service, which offers
free email forwarding at the cost of denoting the DNS records in plain text (you
pay extra for encryption). Another option I haven&#x27;t investigated is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developers.cloudflare.com&#x2F;email-routing&#x2F;get-started&#x2F;enable-email-routing&#x2F;&quot;&gt;Cloudflare Email Routing&lt;&#x2F;a&gt;,
which may be more convenient if Cloudflare doubles as your domain registrar.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s a bummer that I can&#x27;t configure email forwarding for custom domains within
HEY itself, as I can with Fastmail.&lt;&#x2F;p&gt;
&lt;p&gt;The other big missing feature of HEY is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.fastmail.com&#x2F;features&#x2F;masked-email&#x2F;&quot;&gt;masked email&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Fastmail partners with 1Password to offer randomly-generated email addresses
that point to a generic @fastmail domain instead of your personal domain. This
is such a useful (and critical) feature for keeping a clean inbox, since many
newsletter sign-ups or point-of-sale devices (looking at you, Toast) that
collect your email have a tendency to spam without consent. With masked email,
you have the guarantee that if your masked email address gets out in the wild it
can be trivially destroyed with no link back to your other email addresses.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, DuckDuckGo has their own masked email service and it’s totally free:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;duckduckgo.com&#x2F;email&#x2F;&quot;&gt;DuckDuckGo Email Protection&lt;&#x2F;a&gt;. The trade-off is a
one-time download of the DuckDuckGo browser extension that you can remove
afterwards.&lt;&#x2F;p&gt;
&lt;p&gt;Both of these features make me wish that HEY was more invested in privacy and
security. They have a couple of great features that already veer in that
direction, like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;spy-trackers&#x2F;&quot;&gt;tracking-pixel elimination&lt;&#x2F;a&gt;
and the entire concept of the Screener, but they haven&#x27;t added any new privacy
features since the platform launched.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;problem-areas&quot;&gt;Problem areas&lt;&#x2F;h2&gt;
&lt;p&gt;Generally speaking, the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;the-screener&#x2F;&quot;&gt;Screener&lt;&#x2F;a&gt;
is one of the killer features of HEY. Preventing unknown senders from dropping
email directly into your inbox is really nice. It does come with a couple of
trade-offs, however.&lt;&#x2F;p&gt;
&lt;p&gt;For one, joining a mailing list means constant triage of Screener requests.
Every personal email of every participant on that mailing list must be manually
screened. HEY created the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;speakeasy&#x2F;&quot;&gt;Speakeasy code&lt;&#x2F;a&gt; as a pseudo
workaround, but it doesn&#x27;t solve the mailing list issue because it requires a
special code in the subject line of an email.&lt;&#x2F;p&gt;
&lt;p&gt;The second problem with the Screener is pollution of your contact list. When you
screen an email into your inbox, you add that email address to your contacts.
That means your contact list export (which you may create if you migrate email
platforms) is cluttered with truckloads of no-reply email addresses, since many
services use no-reply senders for OTP or transactional emails.&lt;&#x2F;p&gt;
&lt;p&gt;When I originally migrated off of HEY to Fastmail a few years ago (before coming
back) I wrote a script that ran through my contacts archive and removed no-reply
domains with regular expressions. Instead, I wish that allowed senders were
simply stored in a location separate from my email contacts.&lt;&#x2F;p&gt;
&lt;p&gt;The other pain point is around the HEY pricing structure. HEY is divided into
two products: HEY for You, which provides an &lt;code&gt;@hey.com&lt;&#x2F;code&gt; email address, and HEY
for Domains, which allows a single custom domain and some extra features. The
problem is that these two products are mutually exclusive.&lt;&#x2F;p&gt;
&lt;p&gt;By using HEY for Domains, I do not have access to an &lt;code&gt;@hey.com&lt;&#x2F;code&gt; email address, a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;world&#x2F;&quot;&gt;HEY World blog&lt;&#x2F;a&gt;, or the ability to take personal
notes on email threads. If I wanted these features in addition to a custom
domain, I&#x27;d need to pay for both HEY products and manage two separate accounts
in my email inbox (of which I want to do neither).&lt;&#x2F;p&gt;
&lt;p&gt;The split in pricing is made even worse because the extra features offered by
Hey for Domains all revolve around team accounts, e.g. multi-user companies. For
a single HEY user, the HEY for You features are more appealing.&lt;&#x2F;p&gt;
&lt;p&gt;This creates an awkward pricing dynamic for a single-user HEY experience. The
product that I actually want is HEY for You with a single custom domain that
maps both emails to a single account. The &lt;code&gt;@hey.com&lt;&#x2F;code&gt; email address should be a
freebie for HEY for Domain users, as it is with alternative email providers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;i-still-like-it-though&quot;&gt;I still like it though&lt;&#x2F;h2&gt;
&lt;p&gt;Since the last two sections have been dwelling a bit on the negatives, I&#x27;ll end
by saying that I still think HEY is a good product. Not every feature is going
to resonate with every individual (there&#x27;s a good amount of fluff), but the
features that do resonate makes HEY feel like personally-crafted software.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;HEY talks about their general philosophy
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;the-hey-way&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;It&#x27;s worth noting that the HEY for Domains pricing scheme is intended for
multiple users. HEY for Domains used to be branded as &quot;HEY for Work&quot;, if
that&#x27;s any indication of where the pricing awkwardness comes from.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Visualizing Bracket City Puzzles</title>
        <published>2025-04-11T00:00:00+00:00</published>
        <updated>2025-04-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-04-12-visualizing-bracket-city/"/>
        <id>https://mgmarlow.com/words/2025-04-12-visualizing-bracket-city/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-04-12-visualizing-bracket-city/">&lt;p&gt;Lately I&#x27;ve been addicted to a new daily word puzzle game called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.theatlantic.com&#x2F;games&#x2F;bracket-city&#x2F;&quot;&gt;Bracket City&lt;&#x2F;a&gt;. It&#x27;s unique
among competitors because the game isn&#x27;t about rearranging letters baked in
hidden information, but rather solving hand-written, crossword-style clues.&lt;&#x2F;p&gt;
&lt;p&gt;I recommend giving the daily puzzle a shot before reading the rest of this
article since it will help with visualizing the puzzle format. But as a quick
rules summary:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A Bracket City solution is a short phrase&lt;&#x2F;li&gt;
&lt;li&gt;Certain words are substituted with clues, indicated via a pair of square
brackets&lt;&#x2F;li&gt;
&lt;li&gt;Clues can nest other clues&lt;&#x2F;li&gt;
&lt;li&gt;You must solve the inner-most clues before you can solve the outer-most&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Since Bracket City is basically a recursive crossword, the structure of a puzzle
is easily mapped to a tree. And so, in classic programmer-brain fashion, I built
a little app that turns a Bracket City puzzle into an interactive tree.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;&#x2F;h2&gt;
&lt;p&gt;I had a couple of realizations while working on this little project.&lt;&#x2F;p&gt;
&lt;p&gt;The first was recognizing how brilliant the Bracket City puzzle structure is.
Not only does it spin the age-old crossword in a compelling way that feels
fresh, but the actual mechanics for constructing a Bracket City puzzle are super
simple. It&#x27;s a win in all categories, excellence in design.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The second realization was how easy it is to parse Bracket City puzzles into
trees and render them via Svelte components. I haven&#x27;t done much work with
Svelte, but the ability to recursively render a component by simply
self-referencing that component is incredibly expressive.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re unfamiliar with Svelte, don&#x27;t worry! There&#x27;s really not that much
special Svelte stuff going on in my solution. Most of it is plain old
JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;First thing&#x27;s first: a class for nodes in our tree:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  constructor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; []&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next, the parsing algorithm.&lt;&#x2F;p&gt;
&lt;p&gt;The basic strategy has a function read through the input string one character at
a time. When a &quot;[&quot; is encountered, a new node is created. A couple variables
track our position in the resulting tree:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;currentNode&lt;&#x2F;code&gt; points to the most recent node&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;stack&lt;&#x2F;code&gt; holds a list of nodes in order&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;With &lt;code&gt;currentNode&lt;&#x2F;code&gt;, we can easily append new child nodes to our position in the
tree. With &lt;code&gt;stack&lt;&#x2F;code&gt;, we can exit the &lt;code&gt;currentNode&lt;&#x2F;code&gt; and navigate upwards in the
tree to the node&#x27;s parent.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the algorithm in full:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; parsePuzzle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Initial output takes the form of a single node.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; root&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; currentNode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; stack&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [root]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  for&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;length&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span&gt; char&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; raw[i]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (char&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;[&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      &#x2F;&#x2F; Substitutions are marked with ??.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      currentNode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;??&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      const&lt;&#x2F;span&gt;&lt;span&gt; node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt; new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      currentNode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(node)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      stack&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(node)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      &#x2F;&#x2F; Update our currentNode context so that future nodes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      &#x2F;&#x2F; are appended to the most recent one.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      currentNode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; node&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else if&lt;&#x2F;span&gt;&lt;span&gt; (char&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;]&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      if&lt;&#x2F;span&gt;&lt;span&gt; (stack&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;length&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;        &#x2F;&#x2F; Closing brace encountered, so we can bump the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;        &#x2F;&#x2F; currentNode context up the tree by a single node.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        stack&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;pop&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        currentNode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; stack[stack&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;length&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      currentNode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span&gt; char&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; If we have any elements left over, there&amp;#39;s a missing closing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; brace in the input.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (stack&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;length&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; root]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; root]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The return result of the function denotes whether or not it was successful
followed by the resulting tree, a simple form of error handling.&lt;&#x2F;p&gt;
&lt;p&gt;In Svelte, we can tie this algorithm together with an HTML textarea in a
component like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  import&lt;&#x2F;span&gt;&lt;span&gt; parsePuzzle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;$lib&#x2F;parsePuzzle.js&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; puzzle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; $state&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; $derived&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parsePuzzle&lt;&#x2F;span&gt;&lt;span&gt;(puzzle))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  $inspect&lt;&#x2F;span&gt;&lt;span&gt;(tree)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;textarea&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; bind:value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;{puzzle}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;textarea&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And using the tutorial puzzle as an example,&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# raw input:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[where [opposite of clean] dishes pile up] or [exercise in a [game played with a cue ball]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# tree:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Node(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;quot;?? or ??&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Node(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;quot;where ?? dishes pile up&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Node(&amp;quot;opposite of clean&amp;quot;, [])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Node(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;quot;exercise in a ??&amp;quot;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Node(&amp;quot;game played with a cue ball&amp;quot;, [])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As the textarea is updated, &lt;code&gt;$inspect&lt;&#x2F;code&gt; logs the resulting tree. We haven&#x27;t yet
rendered the tree in the actual UI. Let&#x27;s change that.&lt;&#x2F;p&gt;
&lt;p&gt;First, update the original component to include a new component named &lt;code&gt;Tree&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  import&lt;&#x2F;span&gt;&lt;span&gt; parsePuzzle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;$lib&#x2F;parsePuzzle.js&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  import&lt;&#x2F;span&gt;&lt;span&gt; Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;$lib&#x2F;components&#x2F;Tree.svelte&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span&gt; puzzle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; $state&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;success&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; $derived&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parsePuzzle&lt;&#x2F;span&gt;&lt;span&gt;(puzzle))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;textarea&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; bind:value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;{puzzle}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;textarea&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{#if success}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;Tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; nodes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;{[tree]}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{:else}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Error: brackets are unbalanced&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&#x2F;if}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Creating a new component to handle rendering the puzzle tree is not just to tidy
up the code, it&#x27;s to enable a bit of fancy self-referential Svelte behavior.
Intro CS courses have taught us that tree structures map nicely to recursive
algorithms and it&#x27;s no different when we think about UI components in Svelte.
Svelte allows components to import themselves as a form of recursive rendering.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the &lt;code&gt;Tree&lt;&#x2F;code&gt; component in full:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  import&lt;&#x2F;span&gt;&lt;span&gt; Self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;.&#x2F;Tree.svelte&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; nodes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; $props&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{#each nodes as node}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;{node.text}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;ml-4&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {#if node.children.length &amp;gt; 0}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;Self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; nodes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;{node.children}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&#x2F;if}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&#x2F;each}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;How about that? A Svelte component can render itself by simply importing itself
as a regular old Svelte file. In the template content of the component, we
simply map over our list of nodes and render their text content. If a given node
has children, we use a &lt;code&gt;Self&lt;&#x2F;code&gt; reference to repeat the same process from the
viewpoint of the children.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;ml-4&lt;&#x2F;code&gt; applies left-margin to each of the children nodes, enabling stair-like
nesting throughout the tree. We never need to increment the margin in subsequent
child nodes because the document box model handles the hard work for us. Each
margin is relative to its container, which itself uses the same margin
indentation.&lt;&#x2F;p&gt;
&lt;p&gt;That about wraps it up! I added a couple extra features to the final version,
namely the ability to show&#x2F;hide individual nodes in the tree. I&#x27;ll leave that as
an exercise for the reader.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Well, there is &lt;em&gt;one&lt;&#x2F;em&gt; thing that is maybe questionable about the design of
Bracket City. The layout of the puzzle makes you really want to solve the
outer-most clue before the inner-most, if you know the answer. However the
puzzle forces you to solve the inner-most clues first. This is a
surprisingly controversial design choice!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Onboarding a new Mac</title>
        <published>2025-04-05T00:00:00+00:00</published>
        <updated>2025-04-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-04-05-onboarding-a-new-mac/"/>
        <id>https://mgmarlow.com/words/2025-04-05-onboarding-a-new-mac/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-04-05-onboarding-a-new-mac/">&lt;p&gt;My process for onboarding a new Mac:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Remove all of the apps from the default dock. Move the dock to the righthand
side and set to minimize automatically.&lt;&#x2F;li&gt;
&lt;li&gt;Rebind Caps Lock as Control via Settings-&amp;gt;Keyboard-&amp;gt;Modifier Keys.&lt;&#x2F;li&gt;
&lt;li&gt;Install the usual software:
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.mozilla.org&#x2F;en-US&#x2F;firefox&#x2F;developer&#x2F;&quot;&gt;Firefox Developer Edition&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alacritty.org&quot;&gt;Alacritty&lt;&#x2F;a&gt; (terminal emulator)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rectangleapp.com&quot;&gt;Rectangle&lt;&#x2F;a&gt; (windowing solution)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sourcefoundry.org&#x2F;hack&#x2F;&quot;&gt;Hack&lt;&#x2F;a&gt; (monospace font of choice)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;1password.com&quot;&gt;1Password&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;brew.sh&quot;&gt;Homebrew&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Install git by opening Alacritty, attempting to call &lt;code&gt;git&lt;&#x2F;code&gt;, and accepting the
&lt;code&gt;xcode-select&lt;&#x2F;code&gt; tool installation.&lt;&#x2F;li&gt;
&lt;li&gt;Install must-have brew formulae:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;brew install helix tmux ripgrep npm rbenv&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;authentication&#x2F;connecting-to-github-with-ssh&#x2F;generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent&quot;&gt;Configure a Github SSH key&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Bring over &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;dotfiles&quot;&gt;dotfiles&lt;&#x2F;a&gt; for Alacritty,
Helix, tmux, git, etc. I don&#x27;t have a good workflow for this yet but I&#x27;m
investigating &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;stow&#x2F;&quot;&gt;GNU Stow&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I probably forgot a thing or two, but this list accounts for some 90% of the
tools I use in the day-to-day.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Ruby and RSS feeds</title>
        <published>2025-03-30T00:00:00+00:00</published>
        <updated>2025-03-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-03-30-reading-rss-feeds/"/>
        <id>https://mgmarlow.com/words/2025-03-30-reading-rss-feeds/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-03-30-reading-rss-feeds/">&lt;p&gt;I&#x27;ve been digging into Ruby&#x27;s stdlib RSS parser for a side project and am very
impressed by the overall experience. Here&#x27;s how easy it is to get started:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;open-uri&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;rss&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; URI&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;jvns.ca&#x2F;atom.xml&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That said, doing something interesting with the resulting feed is not quite so
simple.&lt;&#x2F;p&gt;
&lt;p&gt;For one, you can&#x27;t just support RSS. Atom is a more recent standard used by many
blogs (although I think irrelevant in the world of podcasts). There&#x27;s about a
50% split in the use of RSS and Atom in the tiny list of feeds that I follow, so
a feed reader must handle both formats.&lt;&#x2F;p&gt;
&lt;p&gt;Adding Atom support introduces an extra branch to our snippet:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;URI&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;jvns.ca&#x2F;atom.xml&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; case&lt;&#x2F;span&gt;&lt;span&gt; feed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Rss&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;title&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Atom&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Feed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;content&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The need to handle both standards independently is kind of frustrating.&lt;&#x2F;p&gt;
&lt;p&gt;That said, it does make sense from a library perspective. The RSS gem is
principally concerned with parsing XML per the RSS and Atom standards, returning
objects that correspond one-to-one. Any conveniences for general feed reading
are left to the application.&lt;&#x2F;p&gt;
&lt;p&gt;Wrapping the RSS gem in another class helps encapsulate differences in
standards:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; FeedReader&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  attr_reader&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;title&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; initialize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    @url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; url&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; URI&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;@url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) { |&lt;&#x2F;span&gt;&lt;span&gt;r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    case&lt;&#x2F;span&gt;&lt;span&gt; feed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Rss&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      @title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;title&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Atom&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Feed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      @title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;content&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Worse than dealing with competing standards is the fact that not everyone
publishes the content of an article as part of their feed. Many bloggers only
use RSS as a link aggregator that points subscribers to their webpage, omitting
the content entirely:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;rss&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;2.0&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Redacted Blog&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;https:&#x2F;&#x2F;www.redacted.io&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;This is my blog&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Article title goes here&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;https:&#x2F;&#x2F;www.redacted.io&#x2F;this-is-my-blog&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;pubDate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Thu, 25 Jul 2024 00:00:00 GMT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;pubDate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      &amp;lt;!-- No content! --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;item&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;rss&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;How do RSS readers handle this situation? The solution varies based on the app.&lt;&#x2F;p&gt;
&lt;p&gt;The two I&#x27;ve tested, NetNewsWire and Readwise Reader, manage to include the
entire article content in the app, despite the RSS feed omitting it (assuming no
paywalls). My guess is these services make an HTTP request to the source,
scraping the resulting HTML for the article content and ignoring everything
else.&lt;&#x2F;p&gt;
&lt;p&gt;Firefox users are likely familiar with a feature called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;support.mozilla.org&#x2F;en-US&#x2F;kb&#x2F;firefox-reader-view-clutter-free-web-pages&quot;&gt;Reader View&lt;&#x2F;a&gt;
that transforms a webpage into its bare-minimum content. All of the layout
elements are removed in favor of highlighting the text of the page. The JS
library that Firefox uses is open source on their Github:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mozilla&#x2F;readability&quot;&gt;mozilla&#x2F;readability&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;On the Ruby side of things there&#x27;s a handy port called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;cantino&#x2F;ruby-readability&quot;&gt;ruby-readability&lt;&#x2F;a&gt; that we can use
to extract omitted article content directly from the associated website:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;ruby-readability&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;URI&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;jvns.ca&#x2F;atom.xml&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;raw&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; case&lt;&#x2F;span&gt;&lt;span&gt; feed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Rss&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;items&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;first&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;link&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; RSS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Atom&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Feed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    feed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;entries&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;first&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;href&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # Raw HTML content&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  source&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; URI&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;read&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # Just the article HTML content&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  article_content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Readability&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Document&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;source&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;content&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So far the results are good, but I haven&#x27;t tested it on many blogs.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reminiscing on Flow</title>
        <published>2025-03-01T00:00:00+00:00</published>
        <updated>2025-03-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-03-01-reminiscing-on-flow/"/>
        <id>https://mgmarlow.com/words/2025-03-01-reminiscing-on-flow/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-03-01-reminiscing-on-flow/">&lt;p&gt;&lt;em&gt;(The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;flow.org&#x2F;&quot;&gt;type-checker&lt;&#x2F;a&gt;, not the state of deep work)&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;React&#x27;s recent
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;react.dev&#x2F;blog&#x2F;2025&#x2F;02&#x2F;14&#x2F;sunsetting-create-react-app&quot;&gt;sunsetting of Create React App&lt;&#x2F;a&gt;
has me feeling nostalgic.&lt;&#x2F;p&gt;
&lt;p&gt;My first experience with a production web application was a React ecommerce site
built with Create React App. I came into the team with zero React experience,
hot off of some Angular 2 work and eager to dive into a less-opinionated
framework. The year was 2018 and the team (on the frontend, just two of us) was
handed the keys to a brand new project that we could scaffold using whatever
tools we thought best fit the job.&lt;&#x2F;p&gt;
&lt;p&gt;We knew we wanted to build something with React, but debated two alternative
starting templates:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create React App (then, newly released) with Flow&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;One of the many community-maintained templates with TypeScript&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;You might be surprised that Create React App didn&#x27;t originally come bundled with
TypeScript&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, but the ecosystem was at a very different place back in 2018.
Instead, the default type-checker for React applications was Flow, Facebook&#x27;s
own type-checking framework.&lt;&#x2F;p&gt;
&lt;p&gt;After a couple prototypes, we chose Flow. It felt like a safer bet, since it was
built by the same company as the JavaScript framework that powered our app. Flow
also handled some React-isms more gracefully than TypeScript, particularly
higher-order components where integrations with third-party libraries (e.g.
React Router, Redux) led to very complicated scenarios with generics.&lt;&#x2F;p&gt;
&lt;p&gt;Of all of our stack choices at the start of this project in 2018, choosing Flow
is the one that aged the worst. Today, TypeScript is so ubiquitous that removing
it from your open source project
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;world.hey.com&#x2F;dhh&#x2F;open-source-hooliganism-and-the-typescript-meltdown-a474bfda&quot;&gt;incites a community outrage&lt;&#x2F;a&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
Why is TypeScript widely accepted as the de facto way to write JavaScript apps,
whereas Flow never took off?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;flow-vs-typescript.png&quot; alt=&quot;npmtrends: Flow vs. TypeScript&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I chalk it up to a few different reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;TypeScript being a superset of JavaScript allowed early adopters to take
advantage of JavaScript class features (and advanced proposals, like
decorators). In a pre-hooks era, both Angular and React required class syntax
for components and the community seemed to widely support using TypeScript as
a language superset as opposed to just a type-checker.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Full adoption by Angular 2 led to lots of community-driven support for
TypeScript types accompanying major libraries via DefinitelyTyped. Meanwhile
nobody really used Flow outside of React.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Flow alienated users by shipping broad, wide-sweeping breaking changes on a
regular cadence. Maintaining a Flow application felt like being subject to
Facebook&#x27;s whims. Whatever large refactor project was going on at Facebook at
the time felt like it directly impacted your app.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;VSCode has become the standard text editor for new developers and it ships
with built-in support for TypeScript.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;typescript-as-a-language-superset&quot;&gt;TypeScript as a language superset&lt;&#x2F;h2&gt;
&lt;p&gt;Philosophically, in 2018 the goals of Flow and TypeScript were quite different.
TypeScript wasn&#x27;t afraid to impose a runtime cost on your application to achieve
certain features, like enums and decorators. These features required that your
build pipeline either used the TypeScript compiler (which was, and is,
incredibly slow) or clobbered together a heaping handful of Babel plugins.&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, Flow promised to be &lt;em&gt;just JavaScript with types&lt;&#x2F;em&gt;, never
making its way into your actual production JavaScript bundle. Since Flow wasn&#x27;t
a superset of JavaScript, it was simple to set up with existing build pipelines.
Just strip the types from the code and you&#x27;re good to go.&lt;&#x2F;p&gt;
&lt;p&gt;Back when JavaScript frameworks were class-based (riding on the hype from
ES2015), I think developers were more receptive towards bundling in additional
language features as part of the normal build pipeline. It was not uncommon to
have a handful of polyfills and experimental language features in every large
JavaScript project. TypeScript embraced this methodology, simplifying the
bundling process by offering support in the TypeScript compiler proper.&lt;&#x2F;p&gt;
&lt;p&gt;Nowadays the stance between the two tools has reversed. The adoption of
alternative bundlers that cannot use the TypeScript compiler (esbuild, SWC, and
so on) has meant that JavaScript developers are much less likely to make use of
TypeScript-specific features. People generally seem less receptive towards
TypeScript-specific features (e.g. enums) if they&#x27;re easily replaced by a
zero-cost alternative (union types). Meanwhile, recent Flow releases added
support for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;flow.org&#x2F;en&#x2F;docs&#x2F;enums&#x2F;&quot;&gt;enums&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;flow.org&#x2F;en&#x2F;docs&#x2F;react&#x2F;component-syntax&#x2F;&quot;&gt;React-specific component syntax&lt;&#x2F;a&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
What a reversal!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;community-library-support&quot;&gt;Community library support&lt;&#x2F;h2&gt;
&lt;p&gt;As TypeScript gathered mindshare among JavaScript developers,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;definitelytyped.org&#x2F;&quot;&gt;DefinitelyTyped&lt;&#x2F;a&gt; crushed
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;flow-typed&#x2F;flow-typed&quot;&gt;FlowTyped&lt;&#x2F;a&gt; in terms of open source
contribution. By the tail end of 2021, our small team had to maintain quite a
few of our own forks of FlowTyped files for many common React libraries
(including React Router and Redux)&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#4&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. Flow definitely felt like an
afterthought for open source library developers.&lt;&#x2F;p&gt;
&lt;p&gt;As TypeScript standardized with npm under the &lt;code&gt;@types&lt;&#x2F;code&gt; namespace, FlowTyped
still required a separate CLI. It&#x27;s not easy to compete when the alternative
makes installing types as easy as &lt;code&gt;npm install @types&#x2F;my-package&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;breaking-things&quot;&gt;Breaking things&lt;&#x2F;h2&gt;
&lt;p&gt;I remember distinctly that upgrading Flow to new releases was such a drag. Not
only that, but it was a regular occurrence. New Flow releases brought
wide-sweeping changes, often with new syntax and many deprecations. This problem
was so well-known in the community that Flow actually released a blog post on
the subject in 2019:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;flow-type&#x2F;upgrading-flow-codebases-40ef8dd3ccd8&quot;&gt;Upgrading Flow Codebases&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For the most part, I don&#x27;t mind if improvements to Flow means new violations in
my existing codebase pointing to legitimate issues. What I do mind is that many
of these problematic Flow releases felt more like Flow rearchitecting itself
around fundamental issues that propagated down to users as new syntax
requirements. It did not often feel like the cost to upgrade matched the benefit
to my codebase.&lt;&#x2F;p&gt;
&lt;p&gt;A couple examples that I still remember nearly 6 years later:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;flow-type&#x2F;asking-for-required-annotations-64d4f9c1edf8&quot;&gt;Asking for required annotations&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;flow-type&#x2F;types-first-a-scalable-new-architecture-for-flow-3d8c7ba1d4eb&quot;&gt;Types-First: A Scalable New Architecture for Flow&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;lsp-tooling-and-the-rise-of-vscode&quot;&gt;LSP, tooling, and the rise of VSCode&lt;&#x2F;h2&gt;
&lt;p&gt;In the early days, the Flow language server was on par with TypeScript&#x27;s. Both
tools were newly emerging and often ran into issues that required restarting the
language server to re-index your codebase.&lt;&#x2F;p&gt;
&lt;p&gt;VSCode was not as ubiquitous in those days as it is today, though it was
definitely an emerging star. Facebook was actually working on its own IDE at the
time, built on top of Atom. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nuclide.io&#x2F;&quot;&gt;Nuclide&lt;&#x2F;a&gt; promised deep
integration with Flow and React, and gathered a ton of excitement from our team.
Too bad it was
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;atom-editor.cc&#x2F;blog&#x2F;2018&#x2F;12&#x2F;12&#x2F;facebook-retires-nuclide-extension&#x2F;&quot;&gt;retired in December of 2018&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;As time went on and adoption of VSCode skyrocketed, Flow support lagged behind.
The TypeScript language server made huge improvements in consistency and
stability and was pre-installed in every VSCode installation. Meanwhile Flow
crashed with any dependency change, and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;marketplace.visualstudio.com&#x2F;items?itemName=flowtype.flow-for-vscode&quot;&gt;installing the Flow extension&lt;&#x2F;a&gt;
involves digging into your built-in VSCode settings and disabling
JavaScript&#x2F;TypeScript language support.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;towards-typescript&quot;&gt;Towards TypeScript&lt;&#x2F;h2&gt;
&lt;p&gt;As our Flow application grew from 3-month unicorn to 3-year grizzled veteran,
Flow really started to wear developers on our team down. It was a constant
onboarding pain as developers struggled to set up VSCode and cope with some of
the Flow language server idiosyncrasies. Refactoring to TypeScript was an
inevitable conversation repeated with every new hire.&lt;&#x2F;p&gt;
&lt;p&gt;The point of this blog post is not to bag on Flow. I still have a ton of respect
for the project and its original goal of simplicity: &quot;JavaScript with types&quot;.
Although that goals lives on via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jsdoc&#x2F;jsdoc&quot;&gt;JSDoc&lt;&#x2F;a&gt;, Flow
is an important milestone to remember as type annotations are
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tc39.es&#x2F;proposal-type-annotations&#x2F;&quot;&gt;formally discussed by TC39&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Before leaving the company, I remember tasking out a large project detailing the
entire process of converting our Flow codebase to TypeScript. I wonder if it was
ever finished.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;TypeScript support was added in 2019 with the v2 release.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;For another example, see Svelte&#x27;s move from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sveltejs&#x2F;svelte&#x2F;pull&#x2F;8569&quot;&gt;TypeScript to JSDoc&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;The move away from &quot;JavaScript with types&quot; is documented in this blog post:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;flow-type&#x2F;clarity-on-flows-direction-and-open-source-engagement-e721a4eb4d8b&quot;&gt;Clarity on Flow’s Direction and Open Source Engagement&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;4&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;If you&#x27;ve never looked at one of the type files for some of your favorite
libraries, they can be
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;flow-typed&#x2F;flow-typed&#x2F;blob&#x2F;main&#x2F;definitions&#x2F;npm&#x2F;react-redux_v6.x.x&#x2F;flow_v0.104.x-0.141.x&#x2F;react-redux_v6.x.x.js&quot;&gt;rather cryptic&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Zod refinements are complicated</title>
        <published>2025-02-26T00:00:00+00:00</published>
        <updated>2025-02-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-02-26-zod-refinements/"/>
        <id>https://mgmarlow.com/words/2025-02-26-zod-refinements/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-02-26-zod-refinements/">&lt;p&gt;Today I found myself at the bottom of a rabbit hole, exploring how
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zod.dev&quot;&gt;Zod&#x27;s&lt;&#x2F;a&gt; refine method interacts with form validations. As with
most things in programming, reality is never as clear-cut as the types make it
out to be.&lt;&#x2F;p&gt;
&lt;p&gt;Today&#x27;s issue concerns
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;colinhacks&#x2F;zod&#x2F;issues&#x2F;479&quot;&gt;zod&#x2F;issues&#x2F;479&lt;&#x2F;a&gt;, where refine
validations aren&#x27;t executed until all fields in the associated object are
present. Here&#x27;s a reframing of the problem:&lt;&#x2F;p&gt;
&lt;p&gt;The setup:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I have a form with fields A and B. Both are required fields, say &lt;code&gt;required_a&lt;&#x2F;code&gt;
and &lt;code&gt;required_b&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;I have a validation that depends on the values of both A and B, say
&lt;code&gt;complex_a_b&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The problem:&lt;&#x2F;p&gt;
&lt;p&gt;If one of A or B is not filled out, the form parses with errors: &lt;code&gt;[required_a]&lt;&#x2F;code&gt;,
not &lt;code&gt;[required_a, complex_a_b]&lt;&#x2F;code&gt;. In other words, &lt;code&gt;complex_a_b&lt;&#x2F;code&gt; only pops up as
an error when both A and B are filled out.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s an example schema that demonstrates the problem:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; schema&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;object&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;refine&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;values&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt; !&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;complexValidation&lt;&#x2F;span&gt;&lt;span&gt;(values&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; values&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;b)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;complex_a_b error&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This creates an experience where a user fills in A, submits, sees a validation
error pointing at B, fills in B, and sees another validation error pointing at
&lt;code&gt;complex_a_b&lt;&#x2F;code&gt;. The user has to play whack-a-mole with the form inputs to make
sure all of the fields pass validation.&lt;&#x2F;p&gt;
&lt;p&gt;As a programmer, we&#x27;re well-acquainted with error messages that work like this.
And we hate them! Imagine a compiler that suppresses certain errors before
prerequisite ones are fixed.&lt;&#x2F;p&gt;
&lt;p&gt;If you dig deep into the aforementioned issue thread, you&#x27;ll come across the
following solution (credit to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;colinhacks&#x2F;zod&#x2F;issues&#x2F;479#issuecomment-2429834215&quot;&gt;jedwards1211&lt;&#x2F;a&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; base&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;object&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; schema&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;preprocess&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; ctx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span&gt; parsed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; base&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;pick&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;safeParse&lt;&#x2F;span&gt;&lt;span&gt;(input)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (parsed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;success)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; parsed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;complexValidation&lt;&#x2F;span&gt;&lt;span&gt;(a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ctx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;addIssue&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        code&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; z&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;ZodIssueCode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;custom&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;a&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;complex_a_b error&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; input&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;},&lt;&#x2F;span&gt;&lt;span&gt; base)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Look at all of that extra logic! Tragic.&lt;&#x2F;p&gt;
&lt;p&gt;From a type perspective, I understand why Zod doesn&#x27;t endeavor to fix this
particular issue. How can we assert the types of A or B when running the
&lt;code&gt;complex_a_b&lt;&#x2F;code&gt; validation, if types A or B are implicitly optional? To evaluate
them optionally in &lt;code&gt;complex_a_b&lt;&#x2F;code&gt; would defeat the type, &lt;code&gt;z.string()&lt;&#x2F;code&gt;, that
asserts that the field is required.&lt;&#x2F;p&gt;
&lt;p&gt;How did I fix it for my app? I didn&#x27;t. I instead turned to the form library,
applying my special validation via the form API instead of the Zod API. I
concede defeat.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Modularizing Start Emacs</title>
        <published>2025-02-24T00:00:00+00:00</published>
        <updated>2025-02-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-02-24-modularizing-start-emacs/"/>
        <id>https://mgmarlow.com/words/2025-02-24-modularizing-start-emacs/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-02-24-modularizing-start-emacs/">&lt;p&gt;Some folks don&#x27;t want their entire Emacs configuration to live in a single,
thousand-line file. Instead, they break their config into separate modules that
each describe a small slice of functionality. Here&#x27;s how you can achieve this
with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&quot;&gt;Start Emacs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-one-load-your-custom-lisp-directory&quot;&gt;Step one: load your custom lisp directory&lt;&#x2F;h2&gt;
&lt;p&gt;Emacs searches for Emacs Lisp code in the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Lisp-Libraries.html#Lisp-Libraries&quot;&gt;Emacs load path&lt;&#x2F;a&gt;.
By default, Emacs only looks in two places:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&#x2F;path&#x2F;to&#x2F;emacs&#x2F;&amp;lt;version&amp;gt;&#x2F;lisp&#x2F;&lt;&#x2F;code&gt;, which contains the standard modules that
ship with Emacs&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;~&#x2F;.emacs.d&#x2F;elpa&#x2F;&lt;&#x2F;code&gt;, which contains packages installed via &lt;code&gt;package-install&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Neither of these places are suitable for your custom lisp code.&lt;&#x2F;p&gt;
&lt;p&gt;I prefer to have my custom lisp code live within &lt;code&gt;~&#x2F;.emacs.d&#x2F;&lt;&#x2F;code&gt;, since I version
control my entire Emacs configuration as a single repository. Start Emacs adds
&lt;code&gt;~&#x2F;.emacs.d&#x2F;lisp&#x2F;&lt;&#x2F;code&gt; to the load path with this line in &lt;code&gt;init.el&lt;&#x2F;code&gt; (the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Init-File.html&quot;&gt;Init File&lt;&#x2F;a&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;load-path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;expand-file-name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;lisp&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; user-emacs-directory&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Where &lt;code&gt;user-emacs-directory&lt;&#x2F;code&gt; points to &lt;code&gt;~&#x2F;.emacs.d&#x2F;&lt;&#x2F;code&gt;, or wherever it may live on
your machine.&lt;&#x2F;p&gt;
&lt;p&gt;The rest of this guide assumes your load path accepts &lt;code&gt;~&#x2F;.emacs.d&#x2F;lisp&#x2F;&lt;&#x2F;code&gt;, but
feel free to swap out this path for your preferred location.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-two-write-your-module&quot;&gt;Step two: write your module&lt;&#x2F;h2&gt;
&lt;p&gt;Next we&#x27;ll create a module file that adds
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emacs-evil&#x2F;evil&quot;&gt;evil-mode&lt;&#x2F;a&gt; with a few configurations and
extensions.&lt;&#x2F;p&gt;
&lt;p&gt;Create the file &lt;code&gt;evil-module.el&lt;&#x2F;code&gt; in your &lt;code&gt;~&#x2F;.emacs.d&#x2F;lisp&#x2F;&lt;&#x2F;code&gt; directory. Open it
up in Emacs and use &lt;code&gt;M-x auto-insert&lt;&#x2F;code&gt; to fill a bunch of boilerplate Emacs Lisp
content. You can either quickly &lt;code&gt;RET&lt;&#x2F;code&gt; through the prompts or fill them out.
Note: to end the &quot;Keywords&quot; prompt you need to use &lt;code&gt;M-RET&lt;&#x2F;code&gt; instead to signal the
end of a multiple-selection.&lt;&#x2F;p&gt;
&lt;p&gt;Your &lt;code&gt;evil-module.el&lt;&#x2F;code&gt; file should now look something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; evil-module.el ---      &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;-*-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; lexical-binding&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;; -*-&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Copyright (C) 2025&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Author:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Keywords:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; This program is free software; you can redistribute it and&#x2F;or modify&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; it under the terms of the GNU General Public License as published by&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; the Free Software Foundation, either version 3 of the License, or&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; (at your option) any later version.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; This program is distributed in the hope that it will be useful,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; GNU General Public License for more details.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; You should have received a copy of the GNU General Public License&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; along with this program.  If not, see &amp;lt;https:&#x2F;&#x2F;www.gnu.org&#x2F;licenses&#x2F;&amp;gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; Commentary:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; Code:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;provide&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;evil-module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; evil-module.el ends here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Most of these comments aren&#x27;t relevant for your custom lisp module but they&#x27;re
good to have in case you ever want to share your code as an Emacs Lisp package.
The single line of Emacs Lisp code, &lt;code&gt;(provide &#x27;evil-module)&lt;&#x2F;code&gt;, is the most
important part of the template. It denotes &lt;code&gt;&#x27;evil-module&lt;&#x2F;code&gt; as a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;elisp&#x2F;Named-Features.html&quot;&gt;named feature&lt;&#x2F;a&gt;,
allowing us to import it into our Init File.&lt;&#x2F;p&gt;
&lt;p&gt;Since we&#x27;re building an evil-mode module, I&#x27;ll add my preferred Evil defaults to
the file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; Commentary:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Extensions for evil-mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; Code:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; evil&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;setq evil-want-integration t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;setq evil-want-keybinding &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;nil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;evil-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; evil-collection&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;after evil&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;evil-collection-init&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; evil-escape&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;after evil&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;setq evil-escape-key-sequence &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;jj&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;setq evil-escape-delay &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  ;; Prevent &amp;quot;jj&amp;quot; from escaping any mode other than insert-mode.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;setq evil-escape-inhibit-functions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;lambda&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; () (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;not&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;evil-insert-state-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;evil-escape-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;provide&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;evil-module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; evil-module.el ends here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;step-three-require-your-module&quot;&gt;Step three: require your module&lt;&#x2F;h2&gt;
&lt;p&gt;Back in our Init File, we need to signal for Emacs to load our new module
automatically. After the spot where we amended the Emacs load path, go ahead and
require &lt;code&gt;&#x27;evil-module&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; init.el&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;load-path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;expand-file-name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;lisp&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; user-emacs-directory&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;evil-module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Reboot Emacs and your module is ready to go!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Async IO in Emacs</title>
        <published>2025-02-16T00:00:00+00:00</published>
        <updated>2025-02-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-02-16-async-io-emacs/"/>
        <id>https://mgmarlow.com/words/2025-02-16-async-io-emacs/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-02-16-async-io-emacs/">&lt;p&gt;Stumbled on the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;skeeto&#x2F;emacs-aio&quot;&gt;emacs-aio&lt;&#x2F;a&gt; library today
and it&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nullprogram.com&#x2F;blog&#x2F;2019&#x2F;03&#x2F;10&#x2F;&quot;&gt;introduction post&lt;&#x2F;a&gt;. What a
great exploration into how async&#x2F;await works under the hood! I&#x27;m not sure I
totally grok the details, but I&#x27;m excited to dive more into Emacs generators and
different concurrent programming techniques.&lt;&#x2F;p&gt;
&lt;p&gt;The article brings to mind Wiegley&#x27;s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jwiegley&#x2F;emacs-async&quot;&gt;async&lt;&#x2F;a&gt; library, which is probably the
more canonical library for handling async in Emacs. From a brief look at the
README, &lt;code&gt;async&lt;&#x2F;code&gt; looks like it actually spawns independent processes, whereas
&lt;code&gt;emacs-aio&lt;&#x2F;code&gt; is really just a construct for handling non-blocking I&#x2F;O more
conveniently.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;emacs&#x2F;comments&#x2F;128mphh&#x2F;adhoc_async_in_emacslisp_via_generators&#x2F;&quot;&gt;Karthink on reddit&lt;&#x2F;a&gt;
comments on the usability of generators in Emacs:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#x27;ve written small-medium sized packages -- 400 to 2400 lines of elisp -- that
use generators and emacs-aio (async&#x2F;await library built on generator.el) for
their async capabilities. I&#x27;ve regretted it each time: generators in their
current form in elisp are obfuscated, opaque and not introspectable -- you
can&#x27;t debug&#x2F;edebug generator calls. Backtraces are impossible to read because
of the continuation-passing macro code. Their memory overhead is large
compared to using simple callbacks. I&#x27;m not sure about the CPU overhead.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That said, the simplicity of &lt;code&gt;emacs-aio&lt;&#x2F;code&gt; promises is very appealing:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; aio-promise&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;Create a new promise object.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;aio-promise&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defsubst&lt;&#x2F;span&gt;&lt;span&gt; aio-promise-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;object&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;aio-promise&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;type-of&lt;&#x2F;span&gt;&lt;span&gt; object&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;       (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; object&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defsubst&lt;&#x2F;span&gt;&lt;span&gt; aio-result&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;promise&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;aref&lt;&#x2F;span&gt;&lt;span&gt; promise &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Pulling Puzzles from Lichess</title>
        <published>2025-02-03T00:00:00+00:00</published>
        <updated>2025-02-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-02-03-pulling-puzzles-from-lichess/"/>
        <id>https://mgmarlow.com/words/2025-02-03-pulling-puzzles-from-lichess/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-02-03-pulling-puzzles-from-lichess/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lichess.org&quot;&gt;Lichess&lt;&#x2F;a&gt; is an awesome website, made even more awesome by
the fact that it is free and open source. Perhaps lesser known is that the
entire Lichess puzzle database is available for free download under the Creative
Commons CC0 license. Every puzzle that you normally find under
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lichess.org&#x2F;training&quot;&gt;lichess.org&#x2F;training&lt;&#x2F;a&gt; is available for your
perusal.&lt;&#x2F;p&gt;
&lt;p&gt;This is a quick guide for pulling that CSV and seeding a SQLite database so you
can do something cool with it. You will need
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;facebook&#x2F;zstd&quot;&gt;zstd&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;First, &lt;code&gt;wget&lt;&#x2F;code&gt; the file from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;database.lichess.org&#x2F;#puzzles&quot;&gt;Lichess.org open database&lt;&#x2F;a&gt; and save it
into a temporary directory. Run &lt;code&gt;zstd&lt;&#x2F;code&gt; to uncompress it into a CSV that we can
read via Ruby.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;wget&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; https:&#x2F;&#x2F;database.lichess.org&#x2F;lichess_db_puzzle.csv.zst -P tmp&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;zstd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -d tmp&#x2F;lichess_db_puzzle.csv.zst&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;CSV pulled down and uncompressed, it&#x27;s time to read it into the application. I&#x27;m
using Ruby on Rails, so I generate a database model like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bin&#x2F;rails g model Puzzle \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  puzzle_id:string fen:string moves:string rating:integer \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rating_deviation:integer popularity:integer nb_plays:integer \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  themes:string game_url:string opening_tags:string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which creates the following migration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; CreatePuzzles&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ActiveRecord&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Migration&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; change&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    create_table &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;puzzles&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;puzzle_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;fen&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;moves&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;rating&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;rating_deviation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;popularity&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;integer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;nb_plays&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;themes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;game_url&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;opening_tags&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;timestamps&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A separate seed script pulls items from the CSV and bulk-inserts them into
SQLite. I have the following in my &lt;code&gt;db&#x2F;seeds.rb&lt;&#x2F;code&gt;, with a few omitted additions
that check whether or not the puzzles have already been migrated.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;csv_path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Rails&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;root&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;join&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;tmp&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;lichess_db_puzzle.csv&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;raise&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;CSV not found&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; unless&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; File&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;exist?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;csv_path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; []&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;buffer_size&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 500&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;flush&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  Puzzle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;insert_all&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;clear&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;CSV&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;foreach&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;csv_path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; headers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  buffer &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    puzzle_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;PuzzleId&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    fen&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;FEN&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    moves&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Moves&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    rating&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Rating&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    rating_deviation&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;RatingDeviation&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    popularity&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Popularity&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    nb_plays&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;NbPlays&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    themes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Themes&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    game_url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;GameUrl&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    opening_tags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;OpeningTags&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; buffer_size&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    flush&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;flush&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And with that you have the entire Lichess puzzle database available at your
fingertips. The whole process takes less than a minute.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Puzzle&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;where&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;rating &amp;lt; 1700&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;count&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# =&amp;gt; 3035233&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Logseq Has Perfected Note Organization</title>
        <published>2025-02-01T00:00:00+00:00</published>
        <updated>2025-02-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-02-01-logseq-perfected-organization/"/>
        <id>https://mgmarlow.com/words/2025-02-01-logseq-perfected-organization/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-02-01-logseq-perfected-organization/">&lt;p&gt;A little while ago Apple Notes left me with quite the scare. I booted up the app
to jot down an idea and found my entire collection of notes erased. I re-synced
iCloud, nothing. Just the blank welcome screen.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily my notes were still backed up to iCloud, even though they weren&#x27;t
displaying in the app (I checked via the web interface). After 40 minutes of
debugging and toggling a series of obtuse settings, my notes were back on my
phone. Yet the burn remained.&lt;&#x2F;p&gt;
&lt;p&gt;Since then I&#x27;ve been looking at alternatives for my long-term document&#x2F;note
storage. Apple Notes was never meant to be a formal archive of my written work,
it just came out that way due to laziness in moving my notes somewhere
permanent. I investigated the usual suspects: Notion, Obsidian, Bear, Org mode,
good ol&#x27; git and markdown. Nothing stuck. Then I found
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;logseq.com&#x2F;&quot;&gt;Logseq&lt;&#x2F;a&gt; and was immediately smitten.&lt;&#x2F;p&gt;
&lt;p&gt;The truth is, I don&#x27;t actually use Logseq. I use Obsidian. You see, Logseq is a
&lt;em&gt;outliner&lt;&#x2F;em&gt;. Every piece of text is attached to some kind of bulleted list,
whether you&#x27;re writing a code sample or attaching an image. Bulleted lists are
great for notes, but not so great for blog posts or longform writing. I need a
tool that can easily handle standard markdown for this blog, for example.&lt;&#x2F;p&gt;
&lt;p&gt;But despite not actually using Logseq, I&#x27;ve structured my Obsidian identically
to Logseq. The Logseq method of organization is just so good. Everything boils
down to three folders:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;journal&#x2F;&lt;&#x2F;code&gt;: the place for daily notes.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;pages&#x2F;&lt;&#x2F;code&gt;: high-level concepts that link between other pages or entries from
the journal.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;assets&#x2F;&lt;&#x2F;code&gt;: storage for images pasted from clipboard.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That&#x27;s it! Just three folders, each containing a ton of flat files. All of my
actual writing happens in journal pages, titled with the current day in
&lt;code&gt;YYYY-MM-DD&lt;&#x2F;code&gt; format. I never need to think about file organization, nor do I
struggle to find information.&lt;&#x2F;p&gt;
&lt;p&gt;Looking at a long list of &lt;code&gt;YYYY-MM-DD&lt;&#x2F;code&gt; files sounds difficult to navigate, but
the key is that they&#x27;re tagged with links to relevant pages (like
&lt;code&gt;[[disco-elysium]]&lt;&#x2F;code&gt;) that attach the journal entry to a concept. When I want to
view my notes on a concept, I navigate to the concept page (&lt;code&gt;disco-elysium&lt;&#x2F;code&gt;) and
read through the linked mentions. I don&#x27;t need to worry about placing a
particular thought in a particular place because the link doesn&#x27;t care.&lt;&#x2F;p&gt;
&lt;p&gt;I got hooked on this workflow because Logseq is incredible at linked mentions.
Just take a look at this example page:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;logseq-demo.png&quot; alt=&quot;Logseq linked mentions example&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;All of the linked mentions (journal entries containing the tag
&lt;code&gt;[[disco-elysium]]&lt;&#x2F;code&gt;) are directly embedded into the concept page. Logseq will
even embed images, code samples, to-do items, you name it. It works incredibly
well.&lt;&#x2F;p&gt;
&lt;p&gt;The Obsidian equivalent isn&#x27;t quite as nice, but it gets the job done. Obsidian
mentions are briefer, lack context, and stripped of formatting:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;obsidian-demo.png&quot; alt=&quot;Obsidian linked mentions example&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The flip-side is that I don&#x27;t need to write notes in an outline form and can
more easily handle moving my Obsidian notes into plain markdown files for my
blog.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re like me and you want to use Logseq-style features in Obsidian there
are a few configuration settings that are worth knowing about:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;In your Core plugins&#x2F;Daily notes settings, set the New file location to
&lt;code&gt;journal&#x2F;&lt;&#x2F;code&gt; and turn on &quot;Open daily note on startup&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;In Core plugins&#x2F;Backlinks, toggle &quot;Show backlinks at the bottom of notes&quot;.&lt;&#x2F;li&gt;
&lt;li&gt;In Files and links, set the &quot;Default location for new attachments&quot; path to
&lt;code&gt;assets&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;These three settings changes will get you most of the way there. That said,
before messing with those settings I encourage you to give
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;logseq.com&#x2F;&quot;&gt;Logseq&lt;&#x2F;a&gt; a try. It&#x27;s free and open source, it&#x27;s built in
Clojure, and it has an excellent &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discuss.logseq.com&#x2F;&quot;&gt;community forum&lt;&#x2F;a&gt;.
Although I don&#x27;t use it for my longform&#x2F;personal writing, I use it at work where
outlining fits my workflow better.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Paper Puzzle Remixes</title>
        <published>2025-01-11T00:00:00+00:00</published>
        <updated>2025-01-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2025-01-11-paper-puzzle-remixes/"/>
        <id>https://mgmarlow.com/words/2025-01-11-paper-puzzle-remixes/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2025-01-11-paper-puzzle-remixes/">&lt;p&gt;The holidays are always a great time for puzzles. My parents still receive print
newspapers, offering an ideal opportunity to catch up on crosswords. This year I
also picked up &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.nytimes.com&#x2F;events&#x2F;puzzlemania&quot;&gt;NYT&#x27;s Puzzle Mania&lt;&#x2F;a&gt;,
a treasure-trove of paper puzzle goodness. Just a few days ago my partner and I
finished the whopping 50x50 crossword puzzle. That&#x27;s over 1000 clues!&lt;&#x2F;p&gt;
&lt;p&gt;What struck me as especially interesting with Puzzle Mania were the paper
remixes of the popular &quot;-dles&quot;:&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; Wordle, Spelling Bee, and Connections. Each
remix tweaks the digital puzzle form so that it suits a printed medium, changing
a few mechanics but keeping the puzzle evocative of its original design.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;puzzmo.com&quot;&gt;Puzzmo&lt;&#x2F;a&gt; did something similar with their
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;shop.puzzmo.com&#x2F;products&#x2F;puzzmo-crossword-puzzles-vol-1-pack-of-two-identical-books&quot;&gt;Crossword Vol. 1&lt;&#x2F;a&gt;,
offering print versions of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.puzzmo.com&#x2F;game&#x2F;really-bad-chess&quot;&gt;Really Bad Chess&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.puzzmo.com&#x2F;game&#x2F;flip-art&quot;&gt;Flipart&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In fact, when Puzzmo soft-launched they sent out beta invites via physical
postcards to your address. Solve the puzzle on the postcard to unlock your way
into the app.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;puzzmo-invite-letters.jpeg&quot; alt=&quot;Puzzmo beta invite postcards&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The first and third pictured are remixes of Zach Gage games:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.puzzmo.com&#x2F;game&#x2F;typeshift&quot;&gt;Typeshift&lt;&#x2F;a&gt; and Really Bad Chess.
Typeshift is the more interesting of the two, since the digital version relies
on a clever sliding interface to differentiate the game from a simple
wordsearch. Adapting the game to print means the player can no longer find words
by randomly moving the slider up and down.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; It also means lowering the number
of possible words to simplify the search.&lt;&#x2F;p&gt;
&lt;p&gt;I think the popularity of &quot;-dle&quot; puzzle games, the kind of daily games that one
finds on NYT and Puzzmo, have to do with their resemblance to newspaper puzzles.
They&#x27;re short and snackable, perfect while waiting for coffee to brew. They&#x27;re
also crunchy enough that the player makes observable improvements over a long
period of time, often in the form of a solving streak.&lt;&#x2F;p&gt;
&lt;p&gt;However, despite that resemblance there&#x27;s a design tension that arises when
adapting a digital puzzle into a print puzzle. What kinds of mechanics are
translatable and why? How do the designers behind games like Flipart approach
print adaptation of their digital games?&lt;&#x2F;p&gt;
&lt;p&gt;Zach Gage (creator of Flipart) gives some insight into the process in the
Crossword Vol. 1 collection:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;When we first started thinking about what kinds of puzzles we could make in
print, we felt like Flipart was one Puzzmo game that truly could not work on
paper. It was friend and fellow game designer JW Nijman who suggested a grid
with embedded shapes that players would have to draw corresponding shapes on
top of. [...] I didn&#x27;t want players to have to do shape rotation in their
heads (this is tough for many people!), so I brought JW&#x27;s idea to Jack...&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I recommend playing through a game of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.puzzmo.com&#x2F;game&#x2F;flip-art&quot;&gt;Flipart&lt;&#x2F;a&gt; to get a sense of the
difficulties Zach alludes to in this quote. A game of Flipart only takes tens of
seconds. It&#x27;s borderline instinctual; the ocular faculties take control as
shapes rotate to avoid overlapping.&lt;&#x2F;p&gt;
&lt;p&gt;In contrast, the print version of the game is slow and methodical. Rotation is
removed in favor of drawing the shape as-is. The fundamental constraint is
drawing the shape in the grid such that the drawn shape contains the square that
originally depicted it. Shapes cannot rotate and drawings cannot overlap. Print
Flipart is much more of a logic puzzle.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;printflipart.jpg&quot; alt=&quot;The first four print Flipart puzzles&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Both the digital and print forms of Flipart play to the strengths of their
medium. The digital form takes advantage of the fact that the computer can
trivially render shapes in different rotations, something that&#x27;s incredibly
difficult for the human mind (and tedious to draw). The print form remains
evocative of the digital, but ditches rotation in favor of something easier to
both conceptualize and draw.&lt;&#x2F;p&gt;
&lt;p&gt;Converting a digital puzzle to a print puzzle is an interesting exercise. What
can we learn from the process? A few rules come to mind:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep state simple&lt;&#x2F;strong&gt;. Unlike their digital counterparts, print puzzles cannot
represent game state that often changes or changes in unintuitive ways (like
rotations in Flipart). The best print puzzles have the player fill in the game
state as they progress, e.g. letters in crosswords, numbers in sudoku, and
shapes in print Flipart.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Complicated rule evaluations are a better fit for digital puzzles&lt;&#x2F;strong&gt;. Chess
puzzles often feel more like an academic exercise than a casual puzzle, as the
player must not only think about their own optimal move, but also the optimal
response from their opponent. A puzzle that requires multiple back-and-forth
turns quickly balloons into an overwhelming number of possibilities.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rethink UI affordances&lt;&#x2F;strong&gt;. On the web, Typeshift uses a vertical slider to
add extra flavor to the puzzle-solving experience. On paper, implementing a
vertical slider is impossible. To compensate, the overall complexity of the
puzzle is reduced.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Grids make for great playgrounds&lt;&#x2F;strong&gt;. I don&#x27;t think it&#x27;s a coincidence that
crosswords and sudokus are confined to a grid. The grid is satisfying to fill
and clearly denotes progress. It also provides a natural place to store game
state.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I also want to shout-out a fantastic game that released last year:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lok-digital.com&#x2F;&quot;&gt;LOK Digital&lt;&#x2F;a&gt;. It&#x27;s relevant to this whole
conversation because it actually goes in the reverse direction, adapting a print
puzzle into a digital form. Because the rules of LOK are heavily reliant on
rules evaluation, I personally think the digital adaptation is the way to go. It
makes the overall experience quite a bit more enjoyable.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Not my favorite term, but an apt description of the genre after the
popularity of Wordle.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;Not like I&#x27;ve done that before, obviously.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;The print Flipart puzzles are surprisingly similar to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ign.com&#x2F;wikis&#x2F;the-witness&#x2F;Swamp&quot;&gt;tetris puzzles&lt;&#x2F;a&gt; from The
Witness.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Best of 2024</title>
        <published>2024-12-29T00:00:00+00:00</published>
        <updated>2024-12-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-12-29-best-of-2024/"/>
        <id>https://mgmarlow.com/words/2024-12-29-best-of-2024/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-12-29-best-of-2024/">&lt;p&gt;I&#x27;m always surprised when distilling a year into a single post just how many
things take place over those 365 days. When I&#x27;m in the thick of it I&#x27;m rarely
thinking about the details. Events and projects come and go, rarely do I take a
step back and properly register their impact or my feelings. So forgive me a
moment of catharsis.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;game-development&quot;&gt;Game development&lt;&#x2F;h2&gt;
&lt;p&gt;I made a game! It&#x27;s a little game, but I&#x27;m proud of it. It received second place
in a game jam and I think it&#x27;s pretty good (only 25-ish entries in the jam so
reign in the enthusiasm). At the very least, it contains my current best attempt
at level design. Play it for free:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mgmarlow.itch.io&#x2F;kajam2024&quot;&gt;Kat&#x27;s Ghost&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Unsurprisingly the game is a block-pushing puzzle game similar to Sokoban. I say
unsurprisingly because the Sokoban-like has been one of my favorite subgenres of
puzzle games ever since
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;353540&#x2F;Stephens_Sausage_Roll&#x2F;&quot;&gt;Stephen&#x27;s Sausage Roll&lt;&#x2F;a&gt;
(which I haven&#x27;t even finished because it&#x27;s devilishly hard). The Sokoban-like
is the platonic ideal of a puzzle game: all logic, simple controls, simple
constraints.&lt;&#x2F;p&gt;
&lt;p&gt;I also got into crossword construction this year, releasing two midi-sized
American-style crosswords. Both of which are Dungeons and Dragons themed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crosshare.org&#x2F;crosswords&#x2F;4cnnnccIGVfe5B42YqkL&#x2F;dragons-in-dungeons&quot;&gt;Dragons in Dungeons&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crosshare.org&#x2F;crosswords&#x2F;Y5Mj2up2SCMDKqeUTsUx&#x2F;adventure-awaits&quot;&gt;Adventure Awaits&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I tried (and failed) to get the first of those puzzles accepted into
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.puzzmo.com&#x2F;today&quot;&gt;Puzzmo&lt;&#x2F;a&gt; during their open submission period.
Here&#x27;s hoping my next submission does better.&lt;&#x2F;p&gt;
&lt;p&gt;2024 was a big year for puzzles. The availability of free online puzzle games
like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.minutecryptic.com&#x2F;&quot;&gt;Minute Cryptic&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.blockables.app&#x2F;&quot;&gt;Blockables&lt;&#x2F;a&gt;, and the mainstays of Puzzmo or NYT
have made puzzle-solving a daily exercise. We&#x27;re living in the golden ages of
snackable puzzle games. My morning routine has suffered.&lt;&#x2F;p&gt;
&lt;p&gt;This year also marks the release of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;499180&#x2F;Braid_Anniversary_Edition&#x2F;&quot;&gt;Braid Anniversary Edition&lt;&#x2F;a&gt;,
released 16 years after the original. It includes the most in-depth commentary
I&#x27;ve ever seen for a video game, talking game design, programming, art, and
music. It offers a ton of wisdom and has inspired me to create. It&#x27;s also just a
phenomenal game.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;start-and-end-emacs&quot;&gt;Start (and end) Emacs&lt;&#x2F;h2&gt;
&lt;p&gt;Late 2023 and early 2024 I spent quite a bit of time on
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SystemCrafters&#x2F;crafted-emacs&quot;&gt;Crafted Emacs&lt;&#x2F;a&gt; with the goal
of helping folks get started with Emacs. I&#x27;ve always felt that most of the
starter kits pack too much extra stuff into the base Emacs installation, making
for a very complicated or cumbersome first experience. Ditto for distributions
like DOOM or Spacemacs that effectively hijack the built-in Emacs configuration
tools in favor of custom ones (e.g. layers). Crafted Emacs felt like a nice,
intermediate step.&lt;&#x2F;p&gt;
&lt;p&gt;That said, there was still something about Crafted Emacs that prevented me from
recommending it to folks that were interested in switching to Emacs. For one,
the README is that particular breed of verbosity that old-school Emacs hackers
are so fond of. Heavy on the philosophy, light on the examples. For two, the
module system is just inherently complicated. I really wanted to push new Emacs
users towards a single-file configuration, just like how I started.&lt;&#x2F;p&gt;
&lt;p&gt;And so I created &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&quot;&gt;Start Emacs&lt;&#x2F;a&gt;. It&#x27;s
basically just a &quot;better defaults&quot; setup for Emacs with some packages that align
the Emacs and VSCode experience. I&#x27;m particularly happy with the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&#x2F;blob&#x2F;main&#x2F;EXTENDING.md&quot;&gt;extension guide&lt;&#x2F;a&gt;
guide, which moves a lot of the optional configuration into a handful of
recipes.&lt;&#x2F;p&gt;
&lt;p&gt;During the making of Start Emacs I moved back to Windows as my primary dev
machine and was absolutely hating the experience. Emacs mostly worked, but
mainstays like Magit were horribly slow and many packages assumed access to
standard Linux utilities like &lt;code&gt;diff&lt;&#x2F;code&gt; or &lt;code&gt;grep&lt;&#x2F;code&gt;. I spent so many hours messing
around with different Windows development kits (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.msys2.org&#x2F;&quot;&gt;MSYS2&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;skeeto&#x2F;w64devkit&quot;&gt;w64devkit&lt;&#x2F;a&gt;, etc.) but couldn&#x27;t find
something I was happy with. Finally I gave up and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&#x2F;blob&#x2F;main&#x2F;WINDOWS.md&quot;&gt;swapped over to WSL&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This period of Windows hacking had me switching back and forth a few different
text editors while I troubleshooted Emacs, finally motivating me to try out
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;helix-editor.com&#x2F;&quot;&gt;Helix&lt;&#x2F;a&gt;. The
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;helix-editor&#x2F;helix&#x2F;wiki&#x2F;Migrating-from-Vim&quot;&gt;vim-ish keybindings&lt;&#x2F;a&gt;
definitely threw me for a loop, sitting in that awkward area of close enough to
vim that it feels familiar, yet far enough away that I&#x27;m constantly invoking the
wrong commands. But after I garnered enough experience with it I grew to like it
so much that I started questioning my motivations. Why am I spending so much
time setting up Emacs when I have a capable editor already working?&lt;&#x2F;p&gt;
&lt;p&gt;I switched and haven&#x27;t looked back.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve tried writing a blog post about my new setup but I can&#x27;t motivate myself
because it&#x27;s so banal. I use Helix for editing text, tmux to manage terminal
windows (which works excellent in the Windows Terminal, surprisingly), and have
replaced all of my usual Emacs power features with CLI tools like ripgrep or
Awk. I&#x27;m probably not as productive since I still lack familiarity with my
tools, but I&#x27;ve really been enjoying leveraging a console workflow instead of
relying on a GUI editor.&lt;&#x2F;p&gt;
&lt;p&gt;Am I done with Emacs? Probably. Do I still think Emacs is a great tool?
Absolutely! Don&#x27;t let my experience dissuade you from trying it out.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ruby-on-rails&quot;&gt;Ruby on Rails&lt;&#x2F;h2&gt;
&lt;p&gt;This year felt like a great one for
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rubyonrails.org&#x2F;2024&#x2F;12&#x2F;18&#x2F;wrap-up-2024-from-rails-foundation&quot;&gt;Ruby on Rails&lt;&#x2F;a&gt;.
The release of Rails 8 brings a bunch of awesome improvements, including
built-in authentication, full-stack SQLite, and zero-build frontend development.
Folks are talking about Rails again and they&#x27;re doing so with a ton of
enthusiasm.&lt;&#x2F;p&gt;
&lt;p&gt;Coincidentally all of this Rails enthusiasm lines up with a job change for
myself, taking on a new role that does a lot more traditional Rails development.
I&#x27;m thankful that I have the opportunity to work with Ruby everyday.&lt;&#x2F;p&gt;
&lt;p&gt;That said, I&#x27;ve never worked at a Rails shop that actually used Rails for the
frontend. Every single app that I&#x27;ve worked on professionally with Rails has
been an Rails JSON API paired with a SPA frontend, usually React. With SSR
making a big comeback this year (thanks to Hotwire, HTMX, among others) I&#x27;m
eager to dive into the new suite of Rails tools.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;books&quot;&gt;Books&lt;&#x2F;h2&gt;
&lt;p&gt;This year continues a reading trend from the past few years: an exploration into
Japanese literature through Haruki Murakami. Since then I&#x27;ve expanded to another
Japanese-borne author, Kazuo Ishiguro, and am dabbling in the works of Yukio
Mishima. But Murakami still reigns as my most-read author for the third year in
a row.&lt;&#x2F;p&gt;
&lt;p&gt;He&#x27;s especially notable this year thanks to the release of &lt;em&gt;The City and Its
Uncertain Walls&lt;&#x2F;em&gt; in November. Let&#x27;s just say the Murakami excitement was high.&lt;&#x2F;p&gt;
&lt;p&gt;Here are some of my reading highlights for this year:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;p&#x2F;books&#x2F;the-city-and-its-uncertain-walls-haruki-murakami&#x2F;21187339&quot;&gt;&lt;em&gt;The City and Its Uncertain Walls&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;
by Haruki Murakami. I just finished this one last week so it&#x27;s fresh in my
memory. I was surprised at how much of this book rehashes content from
&lt;em&gt;Hard-boiled Wonderland&lt;&#x2F;em&gt;, with the exploration of consciousness as a town
surrounded by a wall. Despite that, I enjoyed the deeper exploration into the
shadow-self. &quot;My real self isn&#x27;t here. It&#x27;s somewhere else. The me that&#x27;s here
looks like me, but is nothing more than a shadow projected onto the ground and
walls...&quot; Quite a few aspects of this novel parallel &lt;em&gt;1Q84&lt;&#x2F;em&gt;, particularly the
protagonist who searches for a long-lost love that rules his heart. &lt;em&gt;The City
and Its Uncertain Walls&lt;&#x2F;em&gt; is an exploration of the self and how it relates to
the world around us.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;p&#x2F;books&#x2F;anathem-neal-stephenson&#x2F;8961850&quot;&gt;&lt;em&gt;Anathem&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; by
Neal Stephenson. I&#x27;ve seen the name Neal Stephenson on many a massive tome at
my local bookstore but haven&#x27;t read any until this year. Now I&#x27;m hooked.
&lt;em&gt;Anathem&lt;&#x2F;em&gt; is a slow novel in every category, but its exploration of
philosophical topics is thorough and endlessly interesting for a layperson
like me. Underpinning the novel is an exploration of realism and nominalism,
depicted through manufactured names created for the world of &lt;em&gt;Anathem&lt;&#x2F;em&gt;. Just
don&#x27;t come to &lt;em&gt;Anathem&lt;&#x2F;em&gt; looking for plot.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;p&#x2F;books&#x2F;1q84-haruki-murakami&#x2F;6864629&quot;&gt;&lt;em&gt;1Q84&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; by Haruki
Murakami. It&#x27;s long, ponderous, and contains one too many Proust references,
but aspects of the work feel cohesive in a way that Murakami&#x27;s other novels
don&#x27;t. I&#x27;m also a sucker for a story about a writer. I am not prepared for a
literary analysis of &lt;em&gt;1Q84&lt;&#x2F;em&gt; though, I was mostly sailing on vibes.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;p&#x2F;books&#x2F;never-let-me-go-kazuo-ishiguro&#x2F;228756&quot;&gt;&lt;em&gt;Never Let Me Go&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;
by Kazuo Ishiguro. I was introduced to Ishiguro from his most latest novel,
&lt;em&gt;Klara and the Sun&lt;&#x2F;em&gt;, which I found to be an enjoyable exploration of empathy,
if a bit superficial on the Sci-Fi implications of an Android protagonist.
&lt;em&gt;Never Let Me Go&lt;&#x2F;em&gt; has similar themes but delivers on them more successfully.
But man, is this book a bummer. Where &lt;em&gt;Klara and the Sun&lt;&#x2F;em&gt; is light and
forgiving, &lt;em&gt;Never Let Me Go&lt;&#x2F;em&gt; is oppressive and unyielding.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I also wanted to shout-out
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;p&#x2F;books&#x2F;the-awk-programming-language-brian-kernighan&#x2F;20334738&quot;&gt;&lt;em&gt;The Awk Programming Language&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;
which had a second edition release late last year that I finished in February.
It&#x27;s unexpectedly one of the best programming books that I&#x27;ve read recently for
a language that I had no prior experience with. I bought the book expecting
perl-ish one-liners for simple problems, but stayed for its profound analysis of
DSLs and Awk as a toolkit for building them. Incredible stuff. These days I have
too much enjoyment searching for problems that I can solve using little Awk
scripts.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;movies&quot;&gt;Movies&lt;&#x2F;h2&gt;
&lt;p&gt;Over the last couple years I&#x27;ve met with a group of friends every weekend to
discuss a movie that one of us picked. A kind of movie-book-club.&lt;&#x2F;p&gt;
&lt;p&gt;The result has been great. I&#x27;m thinking more critically about the media I
consume and my relationship to it. I&#x27;m exposed to other perspectives that
reflect experience I would&#x27;ve never gathered myself. I&#x27;m thankful to have the
opportunity to meet and talk with others about this kind of stuff.&lt;&#x2F;p&gt;
&lt;p&gt;Notable films that I watched this year:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;letterboxd.com&#x2F;film&#x2F;perfect-days-2023&#x2F;&quot;&gt;&lt;em&gt;Perfect Days&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;. I would
describe this film as a personification of Taoism. It follows the daily ritual
of a janitor for
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;The_Tokyo_Toilet&quot;&gt;The Tokyo Toilet&lt;&#x2F;a&gt;, an artsy
urban development project distilled into fancy toilets. The movie is slow and
contemplative and well worth the watch.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;letterboxd.com&#x2F;film&#x2F;vertigo&#x2F;&quot;&gt;&lt;em&gt;Vertigo&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;. Lately I&#x27;ve been on a
little Hitchcock kick, &lt;em&gt;Vertigo&lt;&#x2F;em&gt; being the first of the bunch that I haven&#x27;t
already seen. Unsurprisingly, it&#x27;s great. It&#x27;s a bit slow, but the twists are
worth it.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;letterboxd.com&#x2F;film&#x2F;evil-does-not-exist&#x2F;&quot;&gt;&lt;em&gt;Evil Does Not Exist&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;.
2021&#x27;s &lt;em&gt;Drive My Car&lt;&#x2F;em&gt; is one of my favorite films, period. So I went into
&lt;em&gt;Evil Does Not Exist&lt;&#x2F;em&gt; with high expectations. Unfortunately this one did not
do much for me. There&#x27;s some allegorical storytelling underpinning this movie,
filling in the lines between some light plot elements and nature
cinematography. And while that cinematography is gorgeous, I couldn&#x27;t shake a
sense of boredom at the many extended pauses between beats. Normally
contemplative movies are a hit for me, but this movie didn&#x27;t spark any
thoughts with its storytelling that were worthy of the thoughtful moments.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;letterboxd.com&#x2F;film&#x2F;autumn-sonata&#x2F;&quot;&gt;&lt;em&gt;Autumn Sonata&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;. Speaking of
thoughtful moments. Look, Ingmar Bergman makes excellent movies. &lt;em&gt;Autumn
Sonata&lt;&#x2F;em&gt; is no exception. There&#x27;s a scene in this movie that is a slow pan onto
the face of Liv Ullmann, broadcasting an entire life&#x27;s worth of emotions into
a mere thirty seconds.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;games&quot;&gt;Games&lt;&#x2F;h2&gt;
&lt;p&gt;I was so starved for puzzles after beating Braid that I followed it up by
playing through all of The Talos Principle and about a forth of the sequel. But
neither of those games came out this year, so here&#x27;s a short list of a few
others that sparked my interest.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;499180&#x2F;Braid_Anniversary_Edition&#x2F;&quot;&gt;Braid: Anniversary Edition&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;.
Already mentioned above. Do yourself a favor and pick it up, both for the game
and the commentary.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;3074200&#x2F;The_Rookery&#x2F;&quot;&gt;&lt;em&gt;The Rookery&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;. You
have to be some kind of Chess sicko to get a kick out of this game, but if you
are, it will suck up a ton of your time. It&#x27;s effectively Chess: the
roguelike, but executed incredibly well. It lacks the presentational details
of something like &lt;em&gt;Balatro&lt;&#x2F;em&gt; (another great game this year) but still offers a
tight gameplay loop.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;1147860&#x2F;UFO_50&#x2F;&quot;&gt;&lt;em&gt;UFO 50&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;. An incredible
achievement that is an easy recommendation for anyone remotely interested in
game design. There are so many ideas in this game (well, at least 50) that
twist well-known game mechanics in compelling ways. When I first heard about
this game years ago I thought it was going to be a Warioware-like collection
of minigames. Imagine my surprise when almost every one of the 50 games is
about the length of an original NES title. The fact that this game was ever
finished is an achievement. That it includes so many great games is nothing
short of amazing.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;813230&#x2F;ANIMAL_WELL&#x2F;&quot;&gt;&lt;em&gt;Animal Well&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;. I
have played many metroidvanias over the years but have finished almost none of
them. Animal Well is an exception. It wasn&#x27;t my favorite game to play in 2024
but it was certainly my favorite one to talk about. There was a general sense
of excitement around this title that was infectious, helped along by some
devilish secrets.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;looking-ahead&quot;&gt;Looking ahead&lt;&#x2F;h2&gt;
&lt;p&gt;Not mentioned in this post are a couple months that I spent working on a Chess
engine, or other numerous side projects that have been tabled, resumed, and
tabled again. I&#x27;m thinking a lot about my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;macwright.com&#x2F;2020&#x2F;12&#x2F;24&#x2F;the-new-reading-stack.html&quot;&gt;reading stack&lt;&#x2F;a&gt;,
for lack of a better term. I&#x27;ve been noodling on a few ideas for building my own
Goodreads alternative that doesn&#x27;t have any of the AI cruft from Storygraph,
focused purely on reading and notetaking. We&#x27;ll see where it goes.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m also attempting to break into the world of longform writing, in the way of
nonfiction. In other words, I&#x27;m writing a book. Well, several. Most of my
attempts have suffered the same fate as the average side project, with myself
working furiously until interest wanes, then promptly abandoning the idea.&lt;&#x2F;p&gt;
&lt;p&gt;Eventually one of my many book ideas will make its way into a finished product,
and when that happens I hope those of you still reading this post will enjoy the
result.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Automating Quick Notes with iOS Shortcuts</title>
        <published>2024-12-24T00:00:00+00:00</published>
        <updated>2024-12-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-12-24-notion-ios-shortcut/"/>
        <id>https://mgmarlow.com/words/2024-12-24-notion-ios-shortcut/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-12-24-notion-ios-shortcut/">&lt;p&gt;I&#x27;ve blogged before about why I really dislike apps like Notion for
&lt;a href=&quot;&#x2F;words&#x2F;2024-10-30-why-apple-notes&quot;&gt;taking quick notes&lt;&#x2F;a&gt; since they&#x27;re so slow to
open. The very act of opening the app to take said note often takes 10 or more
seconds, typically with a whole bunch of JavaScript-inflicted loading states and
blank screens. By the time I get to the note, I&#x27;ve already lost my train of
thought.&lt;&#x2F;p&gt;
&lt;p&gt;As it turns out, this painpoint is a perfect candidate for the iOS Shortcuts
app. I can create an automated workflow that captures my text input instantly
but pushes to Notion in the background, allowing me to benefit from Notion&#x27;s
database-like organization but without dealing with the pitiful app performance.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s my Shortcut:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;notion_shortcut.png&quot; alt=&quot;Notion Shortcut Workflow&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Super simple but it gets the job done.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Solving Puzzles by Making Puzzles</title>
        <published>2024-12-19T00:00:00+00:00</published>
        <updated>2024-12-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-12-19-making-puzzles-solving-puzzles/"/>
        <id>https://mgmarlow.com/words/2024-12-19-making-puzzles-solving-puzzles/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-12-19-making-puzzles-solving-puzzles/">&lt;p&gt;This year I&#x27;ve substantially buffed up my crosswording skills. Mon-Wed on the
NYT pose no threat, and I can even occasionally solve the Thu&#x2F;Fri without
checking an answer. Saturday remains befuddling.&lt;&#x2F;p&gt;
&lt;p&gt;One reason for my skill improvement is repetition. The more puzzles I solve, the
more I recognize clue patterns and common words. Drill those puzzles frequently
enough and skill inevitably trickles in.&lt;&#x2F;p&gt;
&lt;p&gt;In reality, repetition only explains a small sliver of my improvement. The bulk
of my newfound skill doesn&#x27;t come from training crossword puzzles out in the
wild, but from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crosshare.org&#x2F;crosswords&#x2F;Y5Mj2up2SCMDKqeUTsUx&#x2F;adventure-awaits&quot;&gt;making my own&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Building a crossword puzzle requires activating a whole bunch of underused brain
wrinkles that remain latent when solving. Thinking of a theme and filling a
bunch of words into a grid is just one small part of the equation. How do I
measure difficulty so solvers don&#x27;t get stuck? How do I compromise in a tradeoff
between word quality and theme? Why does the software keep suggesting I use
Australian birds?&lt;&#x2F;p&gt;
&lt;p&gt;The construction of quality reveals the heart of the puzzle. The very same
questions I ask myself when endeavoring to make a good puzzle help reveal the
construction of puzzles created by other people. For example, I now come
equipped with a backlog of words that appear frequently thanks to their helpful
vowels (&lt;code&gt;OPAL&lt;&#x2F;code&gt;, &lt;code&gt;EMU&lt;&#x2F;code&gt;, &lt;code&gt;ERODE&lt;&#x2F;code&gt;, ...). Difficult corners are made easier when I
consider that the uncommon words are probably grouped with more common words.
Themes are easier to spot now that I have thought of a few of my own.&lt;&#x2F;p&gt;
&lt;p&gt;This same skill applies to other puzzle genres, like the humble
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mgmarlow.itch.io&#x2F;kajam2024&quot;&gt;block-pushing puzzle game&lt;&#x2F;a&gt;. Building
interesting levels is a tough job that requires the constructor to think deeply
about the constraints of their game. I don&#x27;t know about other gamedevs, but I
start by fiddling around with a random level layout, paring things back again
and again until a single core concept is revealed to be interesting. I take that
concept and build three or four levels around it, tutorializing it, expanding
it, and remixing it.&lt;&#x2F;p&gt;
&lt;p&gt;This thought process has me thinking about other block-pushing puzzle games in a
completely different way. Now when I get stuck on
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;1260520&#x2F;Patricks_Parabox&#x2F;&quot;&gt;Patrick&#x27;s Parabox&lt;&#x2F;a&gt;
I take a step back and attempt to reverse engineer the mechanic at play. Why did
the constructor choose &lt;em&gt;this&lt;&#x2F;em&gt; level layout? What mechanic are they trying to
showcase? What am I supposed to take away?&lt;&#x2F;p&gt;
&lt;p&gt;I suppose this same skill applies to programming, in the way of framework
design. As a user of React, I may get frustrated at the hook APIs and the design
of &lt;code&gt;useEffect&lt;&#x2F;code&gt;. But if I pare back the layers and think about what the framework
is fundamentally accomplishing (that is, virtual DOM rendering with a JSX
backend) the thought process of re-renders and &lt;code&gt;useEffect&lt;&#x2F;code&gt; dependencies starts
to reveal itself. Without going out and building my own virtual DOM framework
(something like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;snabbdom&#x2F;snabbdom&quot;&gt;snabbdom&lt;&#x2F;a&gt; is a great
start) it&#x27;s hard to recognize the tradeoffs.&lt;&#x2F;p&gt;
&lt;p&gt;Will constructing crossword puzzles make you a better developer? Almost
certainly not. But it&#x27;s a ton of fun regardless.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The Most Common React Mistake</title>
        <published>2024-12-10T00:00:00+00:00</published>
        <updated>2024-12-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-12-10-common-react-mistake/"/>
        <id>https://mgmarlow.com/words/2024-12-10-common-react-mistake/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-12-10-common-react-mistake/">&lt;p&gt;The React homepage promises that &quot;learning React is learning programming&quot; and I
think the framework somewhat delivers on it. At the very least you don&#x27;t need to
learn a new templating language thanks to JSX.&lt;&#x2F;p&gt;
&lt;p&gt;That said, don&#x27;t be completely fooled by this promise. Like every other
JavaScript framework, React is full of subtle complexities and esoteric nuances
that have nothing to do with the language it&#x27;s programmed in. In vanilla
JavaScript there&#x27;s no such thing as &quot;the rules of hooks&quot; or the need to avoid
mutable variables in favor of &lt;code&gt;useState&lt;&#x2F;code&gt;.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The subject of this post is one such piece of esoteric knowledge that I see
newcomers trip up against when learning React (spoilers: it&#x27;s &lt;code&gt;useEffect&lt;&#x2F;code&gt;). It&#x27;s
a great demonstration of the subtle complexities of React, where the promises of
JavaScript-ness meet the reality of framework design.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problems-of-syncing-async-state&quot;&gt;The problems of syncing async state&lt;&#x2F;h2&gt;
&lt;p&gt;A classic point of friction is the introduction of asynchronous code. You have
some data from the server and you want to render it in your component to
populate the initial values of a form. That last bit is where the bug arises,
forms usually use controlled components which hold onto their values via
&lt;code&gt;useState&lt;&#x2F;code&gt; calls. Attempting to populate the initial value of &lt;code&gt;useState&lt;&#x2F;code&gt; hooks
from asynchronous code inevitably runs into a tricky issue. It&#x27;s easiest to
demonstrate by example.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a simple component that wraps an HTML &lt;code&gt;input&lt;&#x2F;code&gt; and captures its value:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; initialText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; setText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(initialText)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; handleChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;ev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    setText&lt;&#x2F;span&gt;&lt;span&gt;(ev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; onChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;handleChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;react.dev&#x2F;reference&#x2F;react-dom&#x2F;components&#x2F;input#controlling-an-input-with-a-state-variable&quot;&gt;controlled input&lt;&#x2F;a&gt;
because the state variable &lt;code&gt;text&lt;&#x2F;code&gt; dictates the value of &lt;code&gt;input&lt;&#x2F;code&gt;. You might
render &lt;code&gt;MyInput&lt;&#x2F;code&gt; in the template of a form, like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Perhaps even with an initial value by passing the prop &lt;code&gt;initialText&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; initialText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;starting value&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is all fine and dandy. The input correctly initializes with the value of
&lt;code&gt;initialText&lt;&#x2F;code&gt; when passing a string and correctly handles user input.&lt;&#x2F;p&gt;
&lt;p&gt;The problem arises when &lt;code&gt;initialText&lt;&#x2F;code&gt; is asynchronous, as is often the case when
dealing with forms that are populated with data from a server. For example,
introducing a new function &lt;code&gt;getTextFromServer&lt;&#x2F;code&gt; that simulates a 300ms response
time:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; getTextFromServer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;ms&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 300&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;font-weight: bold;&quot;&gt;  new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Promise&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;resolve&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    setTimeout&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      resolve&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;text from server&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    },&lt;&#x2F;span&gt;&lt;span&gt; ms)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;asyncText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; setAsyncText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; async&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      const&lt;&#x2F;span&gt;&lt;span&gt; text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; getTextFromServer&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      setAsyncText&lt;&#x2F;span&gt;&lt;span&gt;(text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    fetch&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  },&lt;&#x2F;span&gt;&lt;span&gt; [])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; initialText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;asyncText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A routine operation in React code: wrap an async fetch call with a &lt;code&gt;useEffect&lt;&#x2F;code&gt;
and monitor the async state with &lt;code&gt;useState&lt;&#x2F;code&gt;. However, run this code and you&#x27;ll
find a bug. Can you spot it in the code?&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the problem: the initial value of &lt;code&gt;MyInput&lt;&#x2F;code&gt; is never populated with the
value of &lt;code&gt;asyncText&lt;&#x2F;code&gt;. It remains blank, even after the &lt;code&gt;getTextFromServer&lt;&#x2F;code&gt;
promise resolves.&lt;&#x2F;p&gt;
&lt;p&gt;Naturally the first step is to log out what&#x27;s going on with &lt;code&gt;initialText&lt;&#x2F;code&gt;. Is
the prop not being updated?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; initialText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(initialText)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here&#x27;s what you&#x27;ll see:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;text from server&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well, actually this looks right. On the first render pass, the value is &lt;code&gt;&quot;&quot;&lt;&#x2F;code&gt;,
the initial value of the &lt;code&gt;useState&lt;&#x2F;code&gt; in the parent. After &lt;code&gt;getTextFromServer&lt;&#x2F;code&gt;
responds with the string &lt;code&gt;&quot;text from server&quot;&lt;&#x2F;code&gt;, that &lt;code&gt;useState&lt;&#x2F;code&gt; is updated and
the child component, &lt;code&gt;MyInput&lt;&#x2F;code&gt;, is re-rendered. It receives the new value of
&lt;code&gt;&quot;text from server&quot;&lt;&#x2F;code&gt; from props.&lt;&#x2F;p&gt;
&lt;p&gt;Well then, how come &lt;code&gt;MyInput&lt;&#x2F;code&gt; is blank?&lt;&#x2F;p&gt;
&lt;p&gt;This is where the most common React mistake is introduced. At this point in
debugging, a new developer searches for a framework solution to this problem. We
just encountered one such solution for handling async state by using
&lt;code&gt;useEffect&lt;&#x2F;code&gt;, what if we were to use it again?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; initialText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; setText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(initialText)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    setText&lt;&#x2F;span&gt;&lt;span&gt;(initialText)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  },&lt;&#x2F;span&gt;&lt;span&gt; [initialText])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; handleChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;ev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    setText&lt;&#x2F;span&gt;&lt;span&gt;(ev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; onChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;handleChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now when the value of the &lt;code&gt;initialText&lt;&#x2F;code&gt; prop updates asynchronously, &lt;code&gt;MyInput&lt;&#x2F;code&gt;
updates to match. The &lt;code&gt;useEffect&lt;&#x2F;code&gt; monitors the dependency change in
&lt;code&gt;initialText&lt;&#x2F;code&gt; and calls &lt;code&gt;setText&lt;&#x2F;code&gt; in response. No more blank input!&lt;&#x2F;p&gt;
&lt;p&gt;Generally when I see this kind of code appear in the wild, it&#x27;s accompanied by
the text &quot;for some reason React isn&#x27;t updating &lt;code&gt;MyInput&lt;&#x2F;code&gt; with the new value of
&lt;code&gt;initialText&lt;&#x2F;code&gt; so I put in a &lt;code&gt;useEffect&lt;&#x2F;code&gt; to keep things in sync.&quot; That &quot;for some
reason&quot; is revealing: something is happening in React-land that I don&#x27;t really
understand, but at least I solved it using a React-like solution.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the rub: sure, this code solves the problem. But it&#x27;s also incredibly
brittle. This solution isn&#x27;t obviously incorrect because developer machines are
fast and we&#x27;re usually dealing with sub-100ms response times from whatever API
we&#x27;re working with. In other words, because of quick response times, a developer
might not notice the pop-in when &lt;code&gt;MyInput&lt;&#x2F;code&gt; is updated with the asynchronous
value.&lt;&#x2F;p&gt;
&lt;p&gt;The thing is slow connections (e.g. mobile phones accessing your application,
server saturation, etc.) will experience increasingly worse pop-in because of
this &lt;code&gt;useEffect&lt;&#x2F;code&gt; change. In the worst-case scenario, a user could type text into
&lt;code&gt;MyInput&lt;&#x2F;code&gt; and have that text cleared away by the &lt;code&gt;useEffect&lt;&#x2F;code&gt; after &lt;code&gt;asyncText&lt;&#x2F;code&gt;
is loaded! Try increasing &lt;code&gt;getTextFromServer&lt;&#x2F;code&gt; to &lt;code&gt;3000&lt;&#x2F;code&gt; and see the result
yourself.&lt;&#x2F;p&gt;
&lt;p&gt;The other problem with this kind of code is that we&#x27;ve effectively doubled the
number of renders of the &lt;code&gt;MyInput&lt;&#x2F;code&gt; component. Sure, in this contrived example
more renders is not doing any harm, but you can imagine that for particularly
complicated components that set 10s of hundreds of different pieces of state,
additional renders are to be avoided. State-syncing code of the kind in this
example often leads to more state-syncing due to extraneous render passes, a
problem that keeps on giving as your application grows.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So what&#x27;s actually happening with the &lt;code&gt;MyInput&lt;&#x2F;code&gt; &lt;code&gt;useState&lt;&#x2F;code&gt;? Why isn&#x27;t it picking
up the new value of &lt;code&gt;initialText&lt;&#x2F;code&gt; from the component prop? The answer is hidden
away in the React documentation (emphasis mine):&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;useState&lt;&#x2F;code&gt; Parameters:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;initialState&lt;&#x2F;code&gt;: The value you want the state to be initially. It can be a
value of any type, but there is a special behavior for functions. &lt;strong&gt;This
argument is ignored after the initial render&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&quot;Ignored after the initial render&quot;, meaning even though the prop &lt;code&gt;initialText&lt;&#x2F;code&gt;
is updated correctly, the &lt;code&gt;useState&lt;&#x2F;code&gt; that wraps &lt;code&gt;text&lt;&#x2F;code&gt; doesn&#x27;t care. It&#x27;s
memoized such that any additional renders of the component will have no effect
on the state variable it encapsulates.&lt;&#x2F;p&gt;
&lt;p&gt;If you think about it, this behavior makes sense. In 90% of cases, you wouldn&#x27;t
want your state variables to be blown away by component re-renders. When you use
&lt;code&gt;useState&lt;&#x2F;code&gt; you expect it to hold onto a value until &lt;code&gt;setState&lt;&#x2F;code&gt; is called, and
the memoization achieves that goal.&lt;&#x2F;p&gt;
&lt;p&gt;Now that we know more about how &lt;code&gt;useState&lt;&#x2F;code&gt; works behind the scenes, we can find
a different solution for the problem of handling asynchronous initial state.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;solution-handle-the-pending-state&quot;&gt;Solution: handle the pending state&lt;&#x2F;h2&gt;
&lt;p&gt;So what should you do instead? The easiest solution is to have the parent
component own the loading state:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ({&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; initialText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; setText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(initialText)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; handleChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;ev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    setText&lt;&#x2F;span&gt;&lt;span&gt;(ev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;value)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; onChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;handleChange&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;isLoading&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; setIsLoading&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span&gt;asyncText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; setAsyncText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; useState&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  useEffect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fetch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; async&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      setIsLoading&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      const&lt;&#x2F;span&gt;&lt;span&gt; text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; getTextFromServer&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      setIsLoading&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;      setAsyncText&lt;&#x2F;span&gt;&lt;span&gt;(text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    fetch&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  },&lt;&#x2F;span&gt;&lt;span&gt; [])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      {&lt;&#x2F;span&gt;&lt;span&gt;isLoading&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ? &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;loading...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt; : &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;MyInput&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; initialText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;asyncText&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;MyInput&lt;&#x2F;code&gt; goes back to its original form: a single &lt;code&gt;useState&lt;&#x2F;code&gt; that accepts
&lt;code&gt;initialText&lt;&#x2F;code&gt; as an argument. Because &lt;code&gt;MyInput&lt;&#x2F;code&gt; is only rendered when
&lt;code&gt;asyncText&lt;&#x2F;code&gt; has been fetched from the server (determined via &lt;code&gt;isLoading&lt;&#x2F;code&gt; in the
parent component) the resulting &lt;code&gt;useState&lt;&#x2F;code&gt; is called once with an initial value
of &lt;code&gt;&quot;text from server&quot;&lt;&#x2F;code&gt;. There&#x27;s no longer any need to sync state because the
initial render of the component has the desired state.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll argue that thinking about loading states is actually the power of avoiding
&lt;code&gt;useEffect&lt;&#x2F;code&gt; to solve these kinds of problems. By moving control of the loading
state up the component hierarchy, developers need to put more thought into the
async nature of their application and how the UI will handle it.&lt;&#x2F;p&gt;
&lt;p&gt;Going back into the discussion of React complexity and the burden of frameworks,
the whole counter-intuitive nature of &lt;code&gt;useState&lt;&#x2F;code&gt; discarding its argument after
the first render is a mind-bender for the beginner. I could imagine spending a
few hours on this problem and getting nowhere because it&#x27;s hard to conceptualize
that the cause is actually within the framework itself, buried in the
implementation detail of memoization in the &lt;code&gt;useState&lt;&#x2F;code&gt; hook. It takes time to
encounter these kinds of issues in React, but spend enough time with it and they
will inevitably rise to the surface.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Although of course there&#x27;s the vanilla JS alternative of needing to
re-render the DOM when you update application state, but that&#x27;s neither here
nor there.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;I want to re-emphasize that I don&#x27;t think the developer is at fault here.
They encountered a subtle problem that is super confusing and solved it
using the tools React gives them. I think it&#x27;s a very natural way of
thinking about things.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;State-syncing begets more state-syncing because the lifecycle of state
values becomes hard to reconcile, and the only solution is to set state
again to ensure everything is the most recent.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Type predicates to avoid casting</title>
        <published>2024-12-03T00:00:00+00:00</published>
        <updated>2024-12-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-12-03-type-predicate/"/>
        <id>https://mgmarlow.com/words/2024-12-03-type-predicate/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-12-03-type-predicate/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.typescriptlang.org&#x2F;docs&#x2F;handbook&#x2F;2&#x2F;narrowing.html#using-type-predicates&quot;&gt;Type predicates&lt;&#x2F;a&gt;
have been around but today I found a particularly nice application. The
situation is this: I have an interface that has an optional field, where the
presence of that field means I need to create a new object on the server, and
the lack of the field means the object has already been created and I&#x27;m just
holding on to it for later. Here&#x27;s what it looked like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;interface&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; File&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; things&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Thing[]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;* ... *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; uploadNewThings&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;things&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; (Thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; File&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;)[]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  Promise&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;all&lt;&#x2F;span&gt;&lt;span&gt;(things&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createThing&lt;&#x2F;span&gt;&lt;span&gt;(thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;blob)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The intersection type &lt;code&gt;Thing &amp;amp; { blob: File }&lt;&#x2F;code&gt; means that &lt;code&gt;uploadNewThings&lt;&#x2F;code&gt; only
accepts &lt;code&gt;things&lt;&#x2F;code&gt; that have the field &lt;code&gt;blob&lt;&#x2F;code&gt;. In other words, things that need to
be created on the server because they have blob content.&lt;&#x2F;p&gt;
&lt;p&gt;However, TypeScript struggles if you try to simply filter the list of &lt;code&gt;things&lt;&#x2F;code&gt;
before passing it into &lt;code&gt;uploadNewThings&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;uploadNewThings&lt;&#x2F;span&gt;&lt;span&gt;(things&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt; !!&lt;&#x2F;span&gt;&lt;span&gt;thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;blob))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The resulting error is this long stream of text:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Argument of type &amp;#39;Thing[]&amp;#39; is not assignable to parameter of type &amp;#39;(Thing &amp;amp; { blob: File; })[]&amp;#39;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Type &amp;#39;Thing&amp;#39; is not assignable to type &amp;#39;Thing &amp;amp; { blob: File; }&amp;#39;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Type &amp;#39;Thing&amp;#39; is not assignable to type &amp;#39;{ blob: File; }&amp;#39;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      Types of property &amp;#39;blob&amp;#39; are incompatible.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Type &amp;#39;File | undefined&amp;#39; is not assignable to type &amp;#39;File&amp;#39;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          Type &amp;#39;undefined&amp;#39; is not assignable to type &amp;#39;File&amp;#39;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The tl;dr being that despite filtering &lt;code&gt;things&lt;&#x2F;code&gt; by &lt;code&gt;thing =&amp;gt; !!thing.blob&lt;&#x2F;code&gt;,
TypeScript does not recognize that the return value is actually
&lt;code&gt;Thing &amp;amp; { blob: File }&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Now you could just cast it,&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;things&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt; !!&lt;&#x2F;span&gt;&lt;span&gt;thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;blob)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; (Thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; File&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;)[]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But casting is bad! It&#x27;s error-prone and doesn&#x27;t &lt;em&gt;really&lt;&#x2F;em&gt; solve the problem that
TypeScript is hinting at. Instead, use a type predicate:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;typescript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; hasBlob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; is&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Thing&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; blob&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; File&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&amp;gt; !!&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;blob&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;uploadNewThings&lt;&#x2F;span&gt;&lt;span&gt;(things&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;filter&lt;&#x2F;span&gt;&lt;span&gt;(hasBlob))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With the type predicate (&lt;code&gt;t is Thing &amp;amp; ...&lt;&#x2F;code&gt;) I can inform TypeScript that I do
in fact know what I&#x27;m doing, and that the call to &lt;code&gt;filter&lt;&#x2F;code&gt; results in a
different interface.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Data migrations with data-migrate</title>
        <published>2024-11-13T00:00:00+00:00</published>
        <updated>2024-11-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-11-13-data-migrate/"/>
        <id>https://mgmarlow.com/words/2024-11-13-data-migrate/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-11-13-data-migrate/">&lt;p&gt;What I traditionally would&#x27;ve used Rake tasks for has been replaced with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ilyakatz&#x2F;data-migrate&quot;&gt;data-migrate&lt;&#x2F;a&gt;, a little gem that
handles data migrations in the same way as Rails schema migrations. It&#x27;s the
perfect way to automate data changes in production, offering a single pattern
for handling data backfills, seed scripts, and the like.&lt;&#x2F;p&gt;
&lt;p&gt;The pros are numerous:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Data migrations are easily generated via CLI and are templated with an &lt;code&gt;up&lt;&#x2F;code&gt;
and &lt;code&gt;down&lt;&#x2F;code&gt; case so folks think about rollbacks.&lt;&#x2F;li&gt;
&lt;li&gt;Just like with Rails schema migrations, there&#x27;s a migration ID kept around
that ensures data migrations are run in order. Old PRs will have merge
conflicts.&lt;&#x2F;li&gt;
&lt;li&gt;You can conditionally run data migrations alongside schema migrations with
&lt;code&gt;bin&#x2F;rails db:migrate:with_data&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It&#x27;s a really neat gem. I&#x27;ll probably still rely on the good ol&#x27; Rake task for
my personal projects, but will doubtless keep &lt;code&gt;data-migrate&lt;&#x2F;code&gt; in the toolbox for
teams.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Cool Rails concerns</title>
        <published>2024-11-09T00:00:00+00:00</published>
        <updated>2024-11-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-11-09-writebook-sluggable/"/>
        <id>https://mgmarlow.com/words/2024-11-09-writebook-sluggable/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-11-09-writebook-sluggable/">&lt;p&gt;There&#x27;s something super elegant about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;once.com&#x2F;writebook&quot;&gt;Writebook&#x27;s&lt;&#x2F;a&gt;
use of concerns. I especially like &lt;code&gt;Book:Sluggable&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Sluggable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  extend&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ActiveSupport&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Concern&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  included &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    before_save &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;generate_slug&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; slug&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;blank?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; generate_slug&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;slug&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parameterize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here&#x27;s a few reasons:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Nesting concerns in a model folder is neat when that concern is an
encapsulation of model-specific functionality: &lt;code&gt;app&#x2F;models&#x2F;book&#x2F;sluggable.rb&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Concerns don&#x27;t have to be big. They do have to be single-purpose.&lt;&#x2F;li&gt;
&lt;li&gt;Reminds me of a great article by Jorge Manrubla:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;dev.37signals.com&#x2F;vanilla-rails-is-plenty&#x2F;&quot;&gt;Vanilla Rails is plenty&lt;&#x2F;a&gt;.
Down with service objects!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Why I Still Use Apple Notes</title>
        <published>2024-10-30T00:00:00+00:00</published>
        <updated>2024-10-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-10-30-why-apple-notes/"/>
        <id>https://mgmarlow.com/words/2024-10-30-why-apple-notes/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-10-30-why-apple-notes/">&lt;p&gt;My list of reasons for using Apple Notes isn&#x27;t very long. In many ways I think
Apple Notes is an inferior app, especially when comparing it to alternatives
like Notion, Obsidian, and the like. There is one thing that keeps me using it,
however. Simplicity.&lt;&#x2F;p&gt;
&lt;p&gt;Organization is a death sentence for spontaneity. Tools like Notion have me
questioning my note placement before I&#x27;ve written a single word, killing the
idea before it&#x27;s had a moment to begin. I&#x27;ve lost track of ideas due to a
distracting home screen (&quot;Oh, I should check my email&quot;), or navigating a Notion
sidebar (&quot;Where&#x27;s the archive again?&quot;), or generating the title of the very note
I&#x27;m starting to write.&lt;&#x2F;p&gt;
&lt;p&gt;Therefore, I value flow over all else. Nothing is more important than getting
the idea into some storage mechanism as fast as humanly possible. Expedience is
the same reason I don&#x27;t carry a paper notebook and a pen, despite preferring
writing by hand to tapping on a piece of glass. When inspiration strikes and I
need to jot down an idea &lt;em&gt;right now&lt;&#x2F;em&gt; nothing beats Apple Notes. Pop open the
app, hit the lower-right corner, type type type.&lt;&#x2F;p&gt;
&lt;p&gt;Every month or so I&#x27;ll peruse my notes and organize them into better, more
permanent places. 90% of the time that means deleting the note. I&#x27;m not very
sentimental&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. The remaining 10% is moved into a GitHub README if it&#x27;s a
project idea or drafted into a proper blog post (markdown + git). This whole
categorization phase is a moment of catharsis and feels wholly productive.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve always been turned off by the term &quot;second brain&quot;. Despite what
productivity gurus say I don&#x27;t think my simple notes will ever amount to some
magnum opus of material, studied by historians in the distant future. Nor do I
think there&#x27;s much value in the nitpicky categorization that makes up a
zettelkasten. I believe those who spend the majority of their time organizing
notes and staring at their Obsidian graph view are fooling themselves into
thinking they&#x27;re more productive. Sure, it&#x27;s pretty. But is my writing any
better?&lt;&#x2F;p&gt;
&lt;p&gt;Productivity software preys upon novelty. New apps tout new approaches that will
always catch me in the allure of efficiency. Let this ode to Apple Notes serve
as a reminder that sometimes simple is better.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Part of my notetaking philosophy is that a note has served its purpose
through the mechanism of its creation. Field Notes puts it well: &quot;I&#x27;m not
writing it down to remember it later, I&#x27;m writing it down to remember it
now.&quot; So when I say 90% I mean it.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Exploring the Writebook Source Code</title>
        <published>2024-10-13T00:00:00+00:00</published>
        <updated>2024-10-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-10-13-exploring-writebook/"/>
        <id>https://mgmarlow.com/words/2024-10-13-exploring-writebook/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-10-13-exploring-writebook/">&lt;p&gt;Earlier this year 37signals released &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;once.com&#x2F;writebook&quot;&gt;Writebook&lt;&#x2F;a&gt;, a
self-hosted book publishing platform. It&#x27;s offering number two from their
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;once.com&#x2F;&quot;&gt;ONCE&lt;&#x2F;a&gt; series, pitched as the antithesis of SaaS. Buy it once
and own it for life, but run it on your own infrastructure.&lt;&#x2F;p&gt;
&lt;p&gt;Unlike the other ONCE offering, Writebook is totally free. When you &quot;purchase&quot;
it through the ONCE checkout, they hook you up with the source code and a
convenient means of downloading the software on a remote service. Since the
software is free (but not open source) I thought it&#x27;s fair game to read through
it and write a little post about its implementation. It&#x27;s not everyday that we
can study a production Rails application made by the same folks behind Rails
itself.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: I&#x27;ll often omit code for the sake of brevity with a &quot;--snip&quot; marker. I
encourage you to download Writebook yourself and follow along so you can
discover the complete context.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;run-the-thing&quot;&gt;Run the thing&lt;&#x2F;h2&gt;
&lt;p&gt;A good place to start is the application entrypoint: &lt;code&gt;Procfile&lt;&#x2F;code&gt;. I think
&lt;code&gt;Procfile&lt;&#x2F;code&gt; is a holdover from the Heroku-era, when everyone was hosting their
Rails applications on free-tier dynos (RIP). Either way, it describes the
top-level processes that make up the server:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;web: bundle exec thrust bin&#x2F;start-app&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;redis: redis-server config&#x2F;redis.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;workers: FORK_PER_JOB=false INTERVAL=0.1 bundle exec resque-pool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nice and simple. There are three main components:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;web&lt;&#x2F;code&gt;, Writebook&#x27;s web and application server&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;redis&lt;&#x2F;code&gt;, the backing database for the application cache and asynchronous
workers&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;workers&lt;&#x2F;code&gt;, the actual process that executes asynchronous tasks&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The only other infrastructure of note is the application database, which is
running as a single file via SQLite3.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;bundle exec thrust bin&#x2F;start-app&lt;&#x2F;code&gt; might be surprising for folks expecting
&lt;code&gt;bin&#x2F;rails server&lt;&#x2F;code&gt; as the main Rails process. &lt;code&gt;thrust&lt;&#x2F;code&gt; is the command invocation
for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;basecamp&#x2F;thruster&#x2F;tree&#x2F;main&quot;&gt;thruster&lt;&#x2F;a&gt;, a fairly recent
HTTP proxy developed by 37signals specifically for ONCE projects. It provides a
similar role to nginx, a web server that sits in front of the main Rails process
to handle static file caching and TLS. The &lt;code&gt;thrust&lt;&#x2F;code&gt; command takes a single
argument, &lt;code&gt;bin&#x2F;start-app&lt;&#x2F;code&gt;, which contains your standard &lt;code&gt;bin&#x2F;rails s&lt;&#x2F;code&gt;
invocation, booting up the application server.&lt;&#x2F;p&gt;
&lt;p&gt;Redis and &lt;code&gt;workers&lt;&#x2F;code&gt; fill out the rest of the stack. Redis fills a few different
purposes for Writebook, serving as the application cache and the task queue for
asynchronous work. I&#x27;m a little surprised
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rails&#x2F;solid_queue&#x2F;&quot;&gt;Solid Queue&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rails&#x2F;solid_cache&quot;&gt;Solid Cache&lt;&#x2F;a&gt; don&#x27;t make an appearance,
swapping out Redis for the primary data store (SQLite in this case). But then
again, perhaps it&#x27;s more cost-efficient to run Redis in this case, since
Writebook probably wants to be self-hosted on minimal hardware (and not have
particular SSD requirements).&lt;&#x2F;p&gt;
&lt;p&gt;You can run the application locally with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ddollar&#x2F;foreman&quot;&gt;foreman&lt;&#x2F;a&gt; (note you&#x27;ll need Redis installed,
as well as libvips for image processing):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;foreman start&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;pages-that-render-markdown&quot;&gt;Pages that render markdown&lt;&#x2F;h2&gt;
&lt;p&gt;When it comes to the textual content of books created with Writebook, everything
boils down to the &lt;code&gt;Page&lt;&#x2F;code&gt; model and it&#x27;s fancy &lt;code&gt;has_markdown :body&lt;&#x2F;code&gt; invocation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Page&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ApplicationRecord&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  has_markdown &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;body&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That single line of code sets up an
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;edgeguides.rubyonrails.org&#x2F;action_text_overview.html&quot;&gt;ActionText&lt;&#x2F;a&gt;
association with &lt;code&gt;Page&lt;&#x2F;code&gt; under the attribute name &lt;code&gt;body&lt;&#x2F;code&gt;. All textual content in
Writebook is stored in the respective ActionText table, saved as raw markdown.
Take a look at this Rails console query for an example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;writebook(dev)&amp;gt; Page.first.body.content&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;=&amp;gt; &amp;quot;# Welcome to Writebook\n\nThanks for downloading Writebook...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To my surprise, &lt;code&gt;has_markdown&lt;&#x2F;code&gt; is not actually a Rails ActionText built-in. It&#x27;s
manually extended into Rails by Writebook in
&lt;code&gt;lib&#x2F;rails_ext&#x2F;action_text_has_markdown.rb&lt;&#x2F;code&gt;, along with a couple other files
that integrate ActionText with the third-party gem
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vmg&#x2F;redcarpet&quot;&gt;redcarpet&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ActionText&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; HasMarkdown&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    extend&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ActiveSupport&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Concern&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    class_methods &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; has_markdown&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; strict_loading&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; strict_loading_by_default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;		# --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        has_one &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;markdown_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;#{&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&amp;quot;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; where&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;          class_name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;ActionText::Markdown&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; inverse_of&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; autosave&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; dependent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;destroy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;          strict_loading&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; strict_loading&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;        # --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ActionText&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Markdown&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Record&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;	# --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mattr_accessor &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;renderer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; default&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Redcarpet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Markdown&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;      Redcarpet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Render&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;HTML&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;DEFAULT_RENDERER_OPTIONS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; DEFAULT_MARKDOWN_EXTENSIONS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    belongs_to &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; polymorphic&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; touch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; to_html&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;renderer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;try&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;call&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;span&gt; renderer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;render&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;html_safe&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;lib&#x2F;rails_ext&#x2F;&lt;&#x2F;code&gt; as the folder name is very intentional. The code belongs in
&lt;code&gt;lib&#x2F;&lt;&#x2F;code&gt; and not &lt;code&gt;app&#x2F;lib&#x2F;&lt;&#x2F;code&gt; because it&#x27;s completely agnostic to the application.
It&#x27;s good ol&#x27; reusable Ruby code for any Rails application that has ActionText.
&lt;code&gt;rails_ext&#x2F;&lt;&#x2F;code&gt; stands for &quot;Rails extension&quot;, a common naming convention for vendor
monkey patches that might live in a Rails application. This code re-opens an
existing namespace (the &lt;code&gt;ActionText&lt;&#x2F;code&gt; module, in this case) and adds new
functionality (&lt;code&gt;ActionText::Markdown&lt;&#x2F;code&gt;). Within the application, users can use
&lt;code&gt;ActionText::Markdown&lt;&#x2F;code&gt; without evet knowing it&#x27;s not a Rails built-in.&lt;&#x2F;p&gt;
&lt;p&gt;This is a neat little implementation for adding markdown support to ActionText,
which is normally just a rich text format coupled to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;trix-editor.org&#x2F;&quot;&gt;Trix editor&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;beyond-pages&quot;&gt;Beyond pages&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;Page&lt;&#x2F;code&gt; is certainly the most important data model when it comes to the core
functionality of Writebook: writing and rendering markdown. The platform
supports a couple other fundamental data types, that being &lt;code&gt;Section&lt;&#x2F;code&gt; and
&lt;code&gt;Picture&lt;&#x2F;code&gt;, that can be assembled alongside &lt;code&gt;Pages&lt;&#x2F;code&gt; to make up an entire &lt;code&gt;Book&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The model hierarchy of a &lt;code&gt;Book&lt;&#x2F;code&gt; looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Book = Leaf[], where Leaf = Page | Section | Picture&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In other words, a &lt;code&gt;Book&lt;&#x2F;code&gt; is made up of many &lt;code&gt;Leaf&lt;&#x2F;code&gt; instances (leaves), where a
&lt;code&gt;Leaf&lt;&#x2F;code&gt; is either a &lt;code&gt;Page&lt;&#x2F;code&gt; (markdown content), a &lt;code&gt;Section&lt;&#x2F;code&gt; (basically a page
break with a title), or a &lt;code&gt;Picture&lt;&#x2F;code&gt; (a full-height image).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;writebook.png&quot; alt=&quot;Writebook book detail screenshot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can see the three different &lt;code&gt;Leaf&lt;&#x2F;code&gt; kinds near the center of the image,
representing the three different types of content that can be added to a &lt;code&gt;Book&lt;&#x2F;code&gt;.
This relationship is clearly represented by the Rails associations in the
respective models:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# app&#x2F;models&#x2F;book.rb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ApplicationRecord&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  has_many &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leaves&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; dependent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;destroy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# app&#x2F;models&#x2F;leaf.rb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ApplicationRecord&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  belongs_to &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; touch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  delegated_type &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leafable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; types&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Leafable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;TYPES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; dependent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;destroy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  positioned_within &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; association&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leaves&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; filter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;active&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Well, maybe not completely &quot;clearly&quot;. One thing that&#x27;s interesting about this
implementation is the use of a Rails concern and &lt;code&gt;delegated_type&lt;&#x2F;code&gt; to represent
the three kinds of leaves:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Leafable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  extend&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ActiveSupport&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Concern&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;  TYPES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; %w[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; Page Section Picture &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  included &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    has_one &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leafable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; inverse_of&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leafable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; touch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    has_one &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; through&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leaf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delegate &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leaf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There are three kinds of &lt;code&gt;Leaf&lt;&#x2F;code&gt; that Writebook supports: &lt;code&gt;Page&lt;&#x2F;code&gt;, &lt;code&gt;Section&lt;&#x2F;code&gt;, and
&lt;code&gt;Picture&lt;&#x2F;code&gt;. Each &lt;code&gt;Leaf&lt;&#x2F;code&gt; contains different attributes according to its kind. A
&lt;code&gt;Page&lt;&#x2F;code&gt; has &lt;code&gt;ActionText::Markdown&lt;&#x2F;code&gt; content, a &lt;code&gt;Section&lt;&#x2F;code&gt; has plaintext, and a
&lt;code&gt;Picture&lt;&#x2F;code&gt; has an image upload and a caption. However, despite their difference
in schema, each of the three &lt;code&gt;Leaf&lt;&#x2F;code&gt; kinds is used in the exact same way by
&lt;code&gt;Book&lt;&#x2F;code&gt;. In other words, &lt;code&gt;Book&lt;&#x2F;code&gt; doesn&#x27;t care which kind of &lt;code&gt;Leaf&lt;&#x2F;code&gt; it holds a
reference to.&lt;&#x2F;p&gt;
&lt;p&gt;This is where &lt;code&gt;delegated_type&lt;&#x2F;code&gt; comes into play. With &lt;code&gt;delegated_type&lt;&#x2F;code&gt;, all of
the shared attributes among our three &lt;code&gt;Leaf&lt;&#x2F;code&gt; kinds live on the &quot;superclass&quot;
record, &lt;code&gt;Leaf&lt;&#x2F;code&gt;. Alongside those shared attributes is a &lt;code&gt;leafable_type&lt;&#x2F;code&gt;, denoting
which &quot;subclass&quot; the &lt;code&gt;Leaf&lt;&#x2F;code&gt; falls into, one of &lt;code&gt;&quot;Page&quot;&lt;&#x2F;code&gt;, &lt;code&gt;&quot;Section&quot;&lt;&#x2F;code&gt;, or
&lt;code&gt;&quot;Picture&quot;&lt;&#x2F;code&gt;. When we call &lt;code&gt;Leaf#leafable&lt;&#x2F;code&gt;, we fetch data from the matching
&quot;subclass&quot; table to pull the non-shared attributes for that &lt;code&gt;Leaf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The pattern is made clear when querying in the Rails console:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;writebook(dev)&amp;gt; Leaf.first.leafable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SELECT &amp;quot;leaves&amp;quot;.* FROM &amp;quot;leaves&amp;quot; ORDER BY &amp;quot;leaves&amp;quot;.&amp;quot;id&amp;quot; ASC LIMIT 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SELECT &amp;quot;pages&amp;quot;.* FROM &amp;quot;pages&amp;quot; WHERE &amp;quot;pages&amp;quot;.&amp;quot;id&amp;quot; = ?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Rails knows from &lt;code&gt;leafable_type&lt;&#x2F;code&gt; that &lt;code&gt;Leaf.first&lt;&#x2F;code&gt; is a &lt;code&gt;Page&lt;&#x2F;code&gt;. To read the rest
of that &lt;code&gt;Leaf&lt;&#x2F;code&gt;&#x27;s attributes, we need to fetch the &lt;code&gt;Page&lt;&#x2F;code&gt; from the &lt;code&gt;pages&lt;&#x2F;code&gt; table
associated to the &lt;code&gt;leafable_id&lt;&#x2F;code&gt; on the record. Same deal for &lt;code&gt;Section&lt;&#x2F;code&gt; and
&lt;code&gt;Picture&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Another thing that&#x27;s interesting about Writebook&#x27;s use of &lt;code&gt;delegated_type&lt;&#x2F;code&gt; is
that the &lt;code&gt;Leaf&lt;&#x2F;code&gt; model isn&#x27;t exposed on a route:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  resources &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;books&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; except&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: %i[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; index show &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    resources &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;sections&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    resources &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;pictures&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    resources &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;pages&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This makes a ton of sense because the concept of &lt;code&gt;Leaf&lt;&#x2F;code&gt; isn&#x27;t exactly
&quot;user-facing&quot;. It&#x27;s more of an implementation detail. The relation between the
three different &lt;code&gt;Leafable&lt;&#x2F;code&gt; types is exposed by some smart inheritance in each of
the &quot;subclasses&quot;. Take &lt;code&gt;SectionsController&lt;&#x2F;code&gt; as an example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; SectionsController&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; LeafablesController&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  private&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; new_leafable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;      Section&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt; leafable_params&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; leafable_params&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      params&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;section&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {}).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;permit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;theme&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;with_defaults&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; default_body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; default_body&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      params&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {})[:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;All of the public controller handlers are implemented in &lt;code&gt;LeafablesController&lt;&#x2F;code&gt;,
presumably because each &lt;code&gt;Leafable&lt;&#x2F;code&gt; is roughly handled in the same way. The only
difference is the params object sent along in the request to create a new
&lt;code&gt;Leaf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; LeafablesController&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ApplicationController&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; create&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    @leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; @book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;press&lt;&#x2F;span&gt;&lt;span&gt; new_leafable&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; leaf_params&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    position_new_leaf @leaf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I appreciate the nomenclature of &lt;code&gt;Book#press&lt;&#x2F;code&gt; to create add a new &lt;code&gt;Leaf&lt;&#x2F;code&gt; to a
&lt;code&gt;Book&lt;&#x2F;code&gt; instance. Very clever.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;authentication-and-users&quot;&gt;Authentication and users&lt;&#x2F;h2&gt;
&lt;p&gt;My go-to when setting up authentication with Rails is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;heartcombo&#x2F;devise&quot;&gt;devise&lt;&#x2F;a&gt; since it&#x27;s an easy drop-in
component. Writebook instead implements its own lightweight authentication
around the built-in &lt;code&gt;has_secure_password&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; User&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ApplicationRecord&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  include&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; Role&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; Transferable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  has_many &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;sessions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; dependent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;destroy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  has_secure_password &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;validations&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; false&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  has_many &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;accesses&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; dependent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;destroy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  has_many &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;books&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; through&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;accesses&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # --snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The authentication domain in Writebook is surprisingly complicated because the
application supports multiple users with different roles and access permissions,
but most of it is revealed through the &lt;code&gt;User&lt;&#x2F;code&gt; model.&lt;&#x2F;p&gt;
&lt;p&gt;The first time you visit a Writebook instance, you&#x27;re asked to provide an email
and password to create the first &lt;code&gt;Account&lt;&#x2F;code&gt; and &lt;code&gt;User&lt;&#x2F;code&gt;. This is represented via a
non-ActiveRecord model class, &lt;code&gt;FirstRun&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; FirstRun&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;  ACCOUNT_NAME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Writebook&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; self.create!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;user_params&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    account&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Account&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; ACCOUNT_NAME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;    User&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;user_params&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;merge&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;role&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;administrator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;tap&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;      DemoContent&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;create_manual&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Whether or not a user can access or edit a book is determined by the
&lt;code&gt;Book::Accessable&lt;&#x2F;code&gt; concern. Basically, a &lt;code&gt;Book&lt;&#x2F;code&gt; has many &lt;code&gt;Access&lt;&#x2F;code&gt; objects
associated with it, each representing a user and a permission. Here&#x27;s the
&lt;code&gt;Access&lt;&#x2F;code&gt; created for the &lt;code&gt;DemoContent&lt;&#x2F;code&gt; referenced in &lt;code&gt;FirstRun&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&amp;lt;Access:0x00007f06efac0538&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  id: 1,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  user_id: 1,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  book_id: 1,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  level: &amp;quot;editor&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  #--snip&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Likewise, when new users are invited to a book, they are assigned an &lt;code&gt;Access&lt;&#x2F;code&gt;
level that matches their permissions (reader or editor). Note that all of this
access-stuff is for books that have not yet been published to the web for public
viewing. Writebook allows you to invite early readers or editors for feedback
before you go live.&lt;&#x2F;p&gt;
&lt;p&gt;Whoa, whoa, whoa. What is this &lt;code&gt;rate_limit&lt;&#x2F;code&gt; on the &lt;code&gt;SessionsController&lt;&#x2F;code&gt;?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; SessionsController&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ApplicationController&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  allow_unauthenticated_access &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;only&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: %i[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; new create &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  rate_limit &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;to&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;             within&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;minutes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;             only&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;: :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;create&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;             with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; render_rejection &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;too_many_requests&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Rails 8 comes with built-in
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rails&#x2F;rails&#x2F;pull&#x2F;50490&quot;&gt;rate limiting support&lt;&#x2F;a&gt;? That&#x27;s
awesome.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;style-notes&quot;&gt;Style notes&lt;&#x2F;h2&gt;
&lt;p&gt;I like the occasional nesting of concerns under model classes, e.g.
&lt;code&gt;Book::Sluggable&lt;&#x2F;code&gt;. These concerns aren&#x27;t reusable (hence the nesting), but they
nicely encapsulate a particular piece of functionality with a callback and a
method.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# app&#x2F;models&#x2F;book&#x2F;sluggable.rb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Book&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Sluggable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  extend&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; ActiveSupport&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Concern&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  included &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    before_save &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;generate_slug&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; slug&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;blank?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; generate_slug&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;slug&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;parameterize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Over on the HTML-side, Writebook doesn&#x27;t depend on a CSS framework. All of the
classes are hand-written and applied in a very flexible,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;css-tricks.com&#x2F;lets-define-exactly-atomic-css&#x2F;&quot;&gt;atomic&lt;&#x2F;a&gt; manner:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;page-toolbar fill-selected align-center gap-half ...&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These classes are grouped together in a single file, &lt;code&gt;utilities.css&lt;&#x2F;code&gt;. Who needs
Tailwind?&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;justify-end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  justify-content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;justify-start&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  justify-content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; start&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;justify-center&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  justify-content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; center&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;justify-space-between&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;  justify-content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; space-between&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;* --snip *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;m also surprised at how little JavaScript is necessary for Writebook. There
are only a handful of StimulusJS controllers, each of which encompasses a tiny
amount of code suited to a generic purpose. The &lt;code&gt;AutosaveController&lt;&#x2F;code&gt; is probably
my favorite:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; Controller&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;@hotwired&#x2F;stimulus&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; submitForm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;helpers&#x2F;form_helpers&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; AUTOSAVE_INTERVAL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;export default class extends&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Controller&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  static&lt;&#x2F;span&gt;&lt;span&gt; classes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;clean&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;dirty&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;saving&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  #timer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Lifecycle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  disconnect&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;submit&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Actions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  async&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; submit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#dirty)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;#save&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  change&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;event&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (event&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;target&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;form&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;element&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;amp;&amp;amp; !&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#dirty)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;      this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;#scheduleSave&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;      this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;#updateAppearance&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Private&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  async&lt;&#x2F;span&gt;&lt;span&gt; #&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;save&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;#updateAppearance&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;#resetTimer&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    await&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; submitForm&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;element)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;#updateAppearance&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  #&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;updateAppearance&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;saving&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; false&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;element&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;classList&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;toggle&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;cleanClass&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; !&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#dirty)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;element&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;classList&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;toggle&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;dirtyClass&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#dirty)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;element&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;classList&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;toggle&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;savingClass&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;&quot;&gt; saving&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  #&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;scheduleSave&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#timer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; setTimeout&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;#save&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; AUTOSAVE_INTERVAL)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  #&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;resetTimer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    clearTimeout&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#timer)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#timer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; null&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  get&lt;&#x2F;span&gt;&lt;span&gt; #&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;dirty&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; !!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;#timer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When you&#x27;re editing markdown content with Writebook, this handy controller
automatically saves your work. I especially appreciate the disconnect handler
that ensures your work is always persisted, even when you navigate out of the
form to another area of the application.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s more to explore here, particularly on the HTML side of things where
Hotwire does a lot of the heavy lifting. Unfortunately I&#x27;m not a good steward
for that exploration since most of my Rails experience involves some sort of
API&#x2F;React split. The nuances of HTML-over-the-wire are over my head.&lt;&#x2F;p&gt;
&lt;p&gt;That said I&#x27;m impressed with Writebook&#x27;s data model, it&#x27;s easy to grok thanks to
some thoughtful naming and strong application of lesser-known Rails features
(e.g. &lt;code&gt;delegated_type&lt;&#x2F;code&gt;). I hope this code exploration was helpful and inspires
the practice of reading code for fun.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>HEY to Fastmail and Back Again</title>
        <published>2024-10-05T00:00:00+00:00</published>
        <updated>2024-10-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-10-05-hey-to-fastmail-back-again/"/>
        <id>https://mgmarlow.com/words/2024-10-05-hey-to-fastmail-back-again/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-10-05-hey-to-fastmail-back-again/">&lt;p&gt;I&#x27;ve read a few stories about folks moving their email from HEY to Fastmail, but
have not seen any in the reverse direction. After two years of Fastmail, I&#x27;m
moving back to HEY. Here are my thoughts.&lt;&#x2F;p&gt;
&lt;p&gt;For those unacquainted with HEY, the main pitch is (a) screen unknown senders
(b) into one of three locations: &quot;Imbox&quot;, &quot;The Feed&quot;, and &quot;Paper Trail&quot;. Senders
that are &quot;screened out&quot; are completely blocked, you won&#x27;t be notified again from
that address. For those &quot;screened in&quot;, the split inbox offers more than just
filters and labels. &quot;The Feed&quot; for example aggregates emails into a continuous
reader view that&#x27;s nice browsing on a weekend morning. There are many
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;&quot;&gt;more features&lt;&#x2F;a&gt; but these two are probably the
most important ones.&lt;&#x2F;p&gt;
&lt;p&gt;In my first HEY adventure, I had an &lt;code&gt;@hey.com&lt;&#x2F;code&gt; address for $99&#x2F;yr. My primary
motivation was moving away from Gmail and freeing some of my dependence on
Google products, which I still maintain is worthwhile. HEY pulled me in with the
marketing, but at $99 I wasn&#x27;t convinced I was receiving enough value for the
price tag. When I saw that Fastmail supported
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;1password.com&#x2F;fastmail&#x2F;&quot;&gt;Masked Email&lt;&#x2F;a&gt;, my mind was made up. Added
privacy at half the cost? Yes please.&lt;&#x2F;p&gt;
&lt;p&gt;So I migrated, eating the cost of cycling yet another email address but setting
up a custom email domain along the way to future-proof my erratic email
exploration tendencies. I followed this
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.francocorrea.com&#x2F;posts&#x2F;moving-from-hey-to-fastmail&quot;&gt;guide from Franco Correa&lt;&#x2F;a&gt;
to emulate some of the HEY functionality in Fastmail, attempting to hold on to
some of the principles that improved my workflow.&lt;&#x2F;p&gt;
&lt;p&gt;Two years later and I&#x27;m moving back to HEY.&lt;&#x2F;p&gt;
&lt;p&gt;Why switch back? The decision mostly comes down to the difference in user
experience between the two apps. Fastmail feels like a chore to use, especially
on iOS where most of my email (and newsletter) reading happens. Here are my two
biggest problems:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I&#x27;d often need to close and reopen the Fastmail app because it was stuck on a
black screen. Particularly frustrating when on a slow connection because it
means going through the whole SPA-style loading animation that can take 10-20
seconds.&lt;&#x2F;li&gt;
&lt;li&gt;Using contacts + groups as substitutes for &quot;The Feed&quot; and &quot;Paper Trail&quot; is
tedious. Email addresses that go into either bucket must first be added to
contacts, then edited to include the appropriate filtering group. I honestly
can&#x27;t remember how to do this in the mobile app.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There were also a handful of workflows that I was missing from HEY:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The ability to merge threads and create collections is incredible when dealing
with travel plans. Rather than juggling a bunch of labels for different trips,
email threads are neatly organized into one spot for each.&lt;&#x2F;li&gt;
&lt;li&gt;&quot;Send me push notifications&quot; on an email thread, which will notify me when
that thread and only that thread receives replies, is genius.&lt;&#x2F;li&gt;
&lt;li&gt;I created a &quot;Set Aside&quot; folder in Fastmail but eventually found myself missing
the nice little stack of email threads that are bundled up in a corner in the
HEY app.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;bundles&#x2F;&quot;&gt;Bundling email from certain senders into a single thread&lt;&#x2F;a&gt;
is an excellent solution for notification streams from Github or Amazon, where
I want to be alerted with updates but don&#x27;t want to have a bunch of separate
email threads taking up space in my inbox.&lt;&#x2F;li&gt;
&lt;li&gt;I really like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hey.com&#x2F;features&#x2F;clips-highlights&#x2F;&quot;&gt;clips&lt;&#x2F;a&gt; as an
alternative to slapping on a label so I know to revisit an email for some
buried content.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Don&#x27;t get me wrong, Fastmail is a great service. If I didn&#x27;t find out that
masked email could be replaced by
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;duckduckgo.com&#x2F;email&#x2F;&quot;&gt;DuckDuckGo Email Protection&lt;&#x2F;a&gt; I would probably
still be using it&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. I&#x27;m especially fond of their investment in
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.fastmail.com&#x2F;blog&#x2F;jmap-new-email-open-standard&#x2F;&quot;&gt;JMAP&lt;&#x2F;a&gt; and attempts
to make the technical ecosystem around email better. Also, if you want to have
multiple custom domains routing to the same email platform, Fastmail is way more
cost effective.&lt;&#x2F;p&gt;
&lt;p&gt;But, having moved back to HEY, I&#x27;ve discovered that I&#x27;m easily swayed by
software that can please and delight. Many of HEY&#x27;s features are UX oddities
that don&#x27;t exactly nail down ways to make email better, but make the experience
of using it more enjoyable. I think HEY gets it right most of the time.&lt;&#x2F;p&gt;
&lt;p&gt;The calendar is a new addition to HEY in the time that I&#x27;ve been away and it&#x27;s
interesting. I&#x27;m not hugely opinionated when it comes to calendars, I hardly use
them outside of work where my company dictates the platform. The HEY calendar
feels split between innovating for the sake of novelty and innovating for the
sake of good ideas.&lt;&#x2F;p&gt;
&lt;p&gt;For one, there&#x27;s no monthly view. Only day and week. Instead of viewing a
complete month you view an endless scroll of weeks, with about three and a half
fitting on the screen at any given time. The daily&#x2F;weekly focus of HEY Calendar
seems catered to daily activities: journaling, photography, and habit tracking.
Not so much complicated scheduling workflows.&lt;&#x2F;p&gt;
&lt;p&gt;HEY&#x27;s email offering still has some rough spots as well:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;No import from an existing email.&lt;&#x2F;li&gt;
&lt;li&gt;Adding additional custom domains is prohibitively expensive for a single user.&lt;&#x2F;li&gt;
&lt;li&gt;Feature rollout is asymmetrical, web and Android often outpace iOS.&lt;&#x2F;li&gt;
&lt;li&gt;Two separate apps for calendar and email (minor, but kind of annoying).&lt;&#x2F;li&gt;
&lt;li&gt;Journal integration with the calendar is interesting, but I&#x27;m hesitant to use
it because there&#x27;s no export.&lt;&#x2F;li&gt;
&lt;li&gt;Can&#x27;t use HEY with an external app (e.g. Thunderbird).&lt;&#x2F;li&gt;
&lt;li&gt;Still can&#x27;t configure swipe actions on iOS.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Some of these (like swipe actions and import) are longtime issues that will
probably never be addressed. It&#x27;s probably also worth noting that the HEY
workflow is rather opinionated and isn&#x27;t guaranteed to hit. But hey, give it a
try and see if it works for you.&lt;&#x2F;p&gt;
&lt;p&gt;Moral of the story: use custom email domains. It protects you from email vendor
lock-in so you&#x27;re free to experiment as you see fit.&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;On that topic, masked email is such a critical privacy feature for email
that I can&#x27;t believe HEY doesn&#x27;t offer it. I suppose the screener is meant
to alleviate that concern (since unwanted emails must be manually
screened-in) but it&#x27;s not quite the same. I&#x27;d rather rest easy knowing that
only a randomly-generated email winds up in marketing garbage lists.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Crafting Interpreters, Ruby Style</title>
        <published>2024-08-18T00:00:00+00:00</published>
        <updated>2024-08-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-08-18-crafting-interpreters/"/>
        <id>https://mgmarlow.com/words/2024-08-18-crafting-interpreters/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-08-18-crafting-interpreters/">&lt;p&gt;I finally have started working through
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;craftinginterpreters.com&#x2F;&quot;&gt;Crafting Interpreters&lt;&#x2F;a&gt;, a wonderful book
about compilers by Robert Nystrom. The book steps through two interpreter
implementations, one in Java and one in C, that ramp in complexity.&lt;&#x2F;p&gt;
&lt;p&gt;Now I don&#x27;t know about you, but I hate Java. I can hardly stand to read it, let
alone write it. That&#x27;s why I decided to write my first Lox interpreter in Ruby,
following along with the book as I can but converting bits and pieces into
Rubyisms as I see fit.&lt;&#x2F;p&gt;
&lt;p&gt;In general, the Java code can be ported 1-1 to Ruby with no changes. Of course
there&#x27;s some obvious stuff, like lack of types means I need fewer methods and no
coersions, or certain stdlib method namespaces that are updated to match Ruby
idioms (&lt;code&gt;while&lt;&#x2F;code&gt; vs. &lt;code&gt;until&lt;&#x2F;code&gt;, anyone?). However, lots of code I just accept as-is
and allow Nystrom to guide me through.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve only worked through the first 7 chapters, but I did note down a few things
in the Ruby conversion that I found interesting.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;avoiding-switch-statement-fallthrough-with-regular-expressions&quot;&gt;Avoiding switch statement fallthrough with regular expressions&lt;&#x2F;h2&gt;
&lt;p&gt;Admittedly this difference is just a tiny syntactical detail, but one that plays
to Ruby&#x27;s strengths. Take the book&#x27;s implementation of &lt;code&gt;scanToken&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;private void&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; scanToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  char&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; advance&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  switch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    case&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; addToken&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;LEFT_PAREN&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; break&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    default:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;isDigit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;isAlpha&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        identifier&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Lox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Unexpected character.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;private boolean&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; isDigit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;char&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; c &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;0&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; c &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;9&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; private boolean isAlpha...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Due to limitations in the Java switch statement, the author adds some
post-fallthrough checks to the &lt;code&gt;default&lt;&#x2F;code&gt; case. This removes the need to check
every number and letter individually (0-9, a-z, A-Z as separate cases) because
the check is deferred into the default case, where an additional conditional
statement is applied. Aesthetically it&#x27;s not an ideal solution since it breaks
up the otherwise regular pattern of &lt;code&gt;case ... handler&lt;&#x2F;code&gt; that holds for the other
tokens. I don&#x27;t know, it&#x27;s just kinda ugly.&lt;&#x2F;p&gt;
&lt;p&gt;With Ruby, I can instead employ regular expressions directly in my switch
statement:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; scan_token&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  case&lt;&#x2F;span&gt;&lt;span&gt; advance&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;(&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    add_token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;left_paren&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &#x2F;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;[:digit:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    number&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &#x2F;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;[:alpha:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    identifier&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;    Lox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;@line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;unexpected character&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;No default fallthrough needed! These tiny details are what keep me programming
in Ruby.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;metaprogramming-the-easy-way&quot;&gt;Metaprogramming the easy way&lt;&#x2F;h2&gt;
&lt;p&gt;The largest deviation between the Java and Ruby implementation is definitely the
metaprogramming. In
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;craftinginterpreters.com&#x2F;representing-code.html#implementing-syntax-trees&quot;&gt;Implementing Syntax Trees&lt;&#x2F;a&gt;
the author employs metaprogramming through an independent build step.&lt;&#x2F;p&gt;
&lt;p&gt;First, a new package is created (&lt;code&gt;com.craftinginterpreters.tool&lt;&#x2F;code&gt;) with a couple
of classes that themselves generate Java classes by writing strings to a file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  private static void&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; defineType&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      PrintWriter&lt;&#x2F;span&gt;&lt;span&gt; writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; String&lt;&#x2F;span&gt;&lt;span&gt; baseName&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      String&lt;&#x2F;span&gt;&lt;span&gt; className&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; String&lt;&#x2F;span&gt;&lt;span&gt; fieldList&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;  static class &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; className &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot; extends &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        baseName &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot; {&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; Constructor.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; className &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; fieldList &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;) {&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; Store parameters in fields.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    String&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span&gt; fields&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; fieldList&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;split&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt; fields&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      String&lt;&#x2F;span&gt;&lt;span&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;split&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;      this.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; name &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot; = &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; name &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;    }&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    &#x2F;&#x2F; Fields.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt; fields&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;    final &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; field &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    writer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;  }&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These string builders are hooked up to a separate entrypoint (made for the
&lt;code&gt;tool&lt;&#x2F;code&gt; Java package) and are compiled separately. The result spits out a bunch
of &lt;code&gt;.java&lt;&#x2F;code&gt; files into the &lt;code&gt;com.craftinginterpreters.lox&lt;&#x2F;code&gt; package, whereby the
programmer checks them into the project.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s not a bad solution by any means, but requiring a separate build step and
metaprogramming by concatenating strings is a little rough. The Ruby solution is
totally different thanks to a bunch of built-in metaprogramming utilities (and
the fact that Ruby is an interpreted language).&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s how I wired up the expression generation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Rlox&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Expr&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;    EXPRESSIONS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Binary&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, [:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Grouping&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, [:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;expression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Literal&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, [:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Unary&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, [:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;    EXPRESSIONS&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;each&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;expression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      classname&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; names &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; expression&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      klass&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Rlox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Expr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;const_set&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;classname&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      klass&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;class_eval&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        attr_accessor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;names&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        define_method&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;initialize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;values&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          names&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;each_with_index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;            instance_variable_set&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;#{&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&amp;quot;,&lt;&#x2F;span&gt;&lt;span&gt; values&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;          end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        define_method&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;accept&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; do&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span&gt;visitor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          visitor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;public_send&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;visit_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;#{&lt;&#x2F;span&gt;&lt;span&gt;classname&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;downcase&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;_expr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;quot;,&lt;&#x2F;span&gt;&lt;span&gt; self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When this file is included into &lt;code&gt;rlox.rb&lt;&#x2F;code&gt; (the main entrypoint to the
interpreter), Ruby goes ahead and builds all of the expression classes
dynamically. No build step needed, just good ol&#x27; Ruby metaprogramming.
&lt;code&gt;Rlox::Expr.const_set&lt;&#x2F;code&gt; adds the class to the scope of the &lt;code&gt;Rlox::Expr&lt;&#x2F;code&gt; module,
re-opening it on the next line via &lt;code&gt;class_eval&lt;&#x2F;code&gt; to add in the
automatically-generated methods.&lt;&#x2F;p&gt;
&lt;p&gt;To close the loop, here&#x27;s what one of the generated classes looks like if it
were to be written out by hand (while also avoiding the dynamic instance
variable setter):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Rlox&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  module&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Expr&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Binary&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      attr_accessor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, :&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;right&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; initialize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        @left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; left&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        @operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; operator&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        @right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; right&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; accept&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;visitor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        visitor&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;visit_binary_expr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Comparing the Ruby and Java implementation is interesting because it highlights
some higher-level advantages and disadvantages between the two languages. With
the Ruby version, adding new types is trivial and does not require an additional
compile + check-in step. Just add a name-argument pair to the &lt;code&gt;EXPRESSIONS&lt;&#x2F;code&gt;
constant and you&#x27;re done!&lt;&#x2F;p&gt;
&lt;p&gt;The flip-side of this is the class is not easily inspectable. Although I wrote
&lt;code&gt;Rlox::Expr::Binary&lt;&#x2F;code&gt; above this paragraph as regular Ruby code, that code
doesn&#x27;t exist anywhere in the application where a programmer&#x27;s eyes can read it.
Instead, developers have to read the metaprogramming code in &lt;code&gt;expr.rb&lt;&#x2F;code&gt; to
understand how the classes work.&lt;&#x2F;p&gt;
&lt;p&gt;I think this implementation leans idiomatic Ruby: metaprogramming is part of the
toolkit so it&#x27;s expected for developers to learn how to deal with it. If you&#x27;re
interested in learning how the class works and can&#x27;t understand the
metaprogramming code, you can always boot up the console and poke around with an
instance of the class. It kind of coincides with the Ruby ethos that a REPL
should be close at hand so you can explore code concepts that you might
otherwise misunderstand by reading the code.&lt;&#x2F;p&gt;
&lt;p&gt;That said, I still have respect for the Java implementation because Ruby
metaprogramming can really end up biting you in the ass.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tdd-well-not-really&quot;&gt;TDD (well, not really)&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;m sure Nystrom omitted tests from the book because it would add a ton of
implementation noise to the project, and not in a way that benefited the
explanation. For my purposes, I wanted to make sure I added tests with each
chapter to make sure my implementation wasn&#x27;t drifting from the expectation.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s not perfect by any means, but it definitely gives me a ton of confidence
that I&#x27;m following along with the material and exercising some of the trickier
edge cases. I was also impressed that Nystrom&#x27;s implementation is really easy to
test. Here&#x27;s an example from the parser:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; TestParser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Minitest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Test&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; test_it_handles_comparison&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;2 &amp;gt; 3&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_instance_of &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Rlox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Expr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Binary&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;greater&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;3.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;2 &amp;gt;= 3&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_instance_of &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Rlox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Expr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Binary&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;greater_equal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;3.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;2 &amp;lt; 3&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_instance_of &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Rlox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Expr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Binary&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;less&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;3.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;2 &amp;lt;= 3&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_instance_of &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Rlox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Expr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;Binary&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;less_equal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;operator&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    assert_equal &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;3.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; got&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    scanner&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Rlox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Scanner&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;str&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; scanner&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;scan_tokens&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Rlox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;Parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # Call private method to bubble up exception that is caught by #parse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    parser&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;send&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;expression&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Astute readers might recognize that the &lt;code&gt;parse&lt;&#x2F;code&gt; helper function defined within
the test is also calling into the &lt;code&gt;Rlox::Scanner&lt;&#x2F;code&gt; class. That&#x27;s one item that
I&#x27;ve taken the quick and easy approach towards: rather than ensure test
isolation by writing out the AST with the &lt;code&gt;Rlox::Expr&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;Rlox::Statement&lt;&#x2F;code&gt; classes
(which are incredibly verbose), I use &lt;code&gt;Rlox::Scanner&lt;&#x2F;code&gt; so I can write my tests as
string expressions that read like the code I&#x27;m testing. Unfortunately, that does
mean that if I write a bug into the &lt;code&gt;Rlox::Scanner&lt;&#x2F;code&gt; class, that bug is
propogated into the &lt;code&gt;Rlox::Parser&lt;&#x2F;code&gt; tests, but in my head it&#x27;s better than the
alternative of tripling the lines of code for my test files. What can you do?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;&#x2F;h2&gt;
&lt;p&gt;There might be a part two for this post as I work my way further through the
first Lox interpreter. If you&#x27;re interested in following along with the code,
check it out on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;rlox&quot;&gt;Github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>New stuff in Emacs 30</title>
        <published>2024-07-28T00:00:00+00:00</published>
        <updated>2025-02-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-07-28-emacs-30-news/"/>
        <id>https://mgmarlow.com/words/2024-07-28-emacs-30-news/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-07-28-emacs-30-news/">&lt;p&gt;Emacs 30.1 is near on the horizon with the most recent pretest (30.0.93) made
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lists.gnu.org&#x2F;archive&#x2F;html&#x2F;emacs-devel&#x2F;2024-12&#x2F;msg00869.html&quot;&gt;available in late December&lt;&#x2F;a&gt;.
This post highlights some new features in the upcoming release that I find
especially compelling.&lt;&#x2F;p&gt;
&lt;p&gt;If you want to know &lt;em&gt;everything&lt;&#x2F;em&gt; that&#x27;s upcoming, check out
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.savannah.gnu.org&#x2F;cgit&#x2F;emacs.git&#x2F;tree&#x2F;etc&#x2F;NEWS?h=emacs-30&quot;&gt;the full release notes&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;native-compilation-enabled-by-default&quot;&gt;Native compilation enabled by default&lt;&#x2F;h2&gt;
&lt;p&gt;This is huge!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;elisp&#x2F;Native-Compilation.html&quot;&gt;Native compilation&lt;&#x2F;a&gt;
was introduced in Emacs 28 behind a configuration flag, so even though it&#x27;s been
around for a little while you probably aren&#x27;t using it unless you compile your
Emacs from source (or use a port that explicitly had it enabled). Enabling it by
default brings it to more users.&lt;&#x2F;p&gt;
&lt;p&gt;This feature compiles Emacs Lisp functions to native code, offering 2-5x faster
performance to the byte-compiled counterpart. The downside is an additional
dependency (&lt;code&gt;libgccjit&lt;&#x2F;code&gt;) and a little extra compilation overhead when installing
a package for the first time. The downsides are so minor that enabling it by
default is a no-brainer.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;native-and-faster-json-support&quot;&gt;Native (and faster) JSON support&lt;&#x2F;h2&gt;
&lt;p&gt;You no longer need an external library (&lt;code&gt;libjansson&lt;&#x2F;code&gt;) to work with JSON in
Emacs. On top of that, JSON parsing performance in Emacs is significantly
improved (the author provides that parsing is up to &lt;strong&gt;8x faster&lt;&#x2F;strong&gt;). This is all
thanks to Géza Herman&#x27;s contribution:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lists.gnu.org&#x2F;archive&#x2F;html&#x2F;emacs-devel&#x2F;2024-03&#x2F;msg00244.html&quot;&gt;I created a faster JSON parser&lt;&#x2F;a&gt;.
He summarizes his changes later in that thread:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;My parser creates Lisp objects during parsing, there is no intermediate step
as Emacs has with jansson. With jansson, there are a lot of allocations, which
my parser doesn&#x27;t have (my parser has only two buffers, which exponentially
grow. There are no other allocations). But even ignoring performance loss
because of mallocs (on my dataset, 40% of CPU time goes into malloc&#x2F;free), I
think parsing should be faster, so maybe jansson is not a fast parser in the
first place.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Great stuff.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;use-package-version-control-support&quot;&gt;use-package version control support&lt;&#x2F;h2&gt;
&lt;p&gt;You can now install packages directly from version-controlled repositories (for
those packages that aren&#x27;t yet in ELPA, Non-GNU ELPA or MELPA).&lt;&#x2F;p&gt;
&lt;p&gt;For example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; bbdb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;vc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt;url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;https:&#x2F;&#x2F;git.savannah.nongnu.org&#x2F;git&#x2F;bbdb.git&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;       :&lt;&#x2F;span&gt;&lt;span&gt;rev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;newest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This also means that you can opt into package updates based on commit instead of
latest release (e.g. &lt;code&gt;:rev :newest&lt;&#x2F;code&gt;). I think this is actually a sleeper feature
of &lt;code&gt;:vc&lt;&#x2F;code&gt;, since the default Emacs package release&#x2F;update cycle can be a little
wonky at times.&lt;&#x2F;p&gt;
&lt;p&gt;If you want all of your &lt;code&gt;:vc&lt;&#x2F;code&gt; packages to prefer the latest commit (instead of
the latest release), you can set &lt;code&gt;use-package-vc-prefer-newest&lt;&#x2F;code&gt; to &lt;code&gt;t&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tree-sitter-modes-are-declared-as-submodes&quot;&gt;Tree-sitter modes are declared as submodes&lt;&#x2F;h2&gt;
&lt;p&gt;I had to read this change a few times before I grokked what it was saying.
Tree-sitter modes, e.g. &lt;code&gt;js-ts-mode&lt;&#x2F;code&gt;, are now submodes of their non-tree-sitter
counterpart, e.g. &lt;code&gt;js-mode&lt;&#x2F;code&gt;. That means any configuration applied to the
non-tree-sitter mode also applies to the tree-sitter mode.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, my &lt;code&gt;.dir-locals.el&lt;&#x2F;code&gt; settings for &lt;code&gt;js-mode&lt;&#x2F;code&gt; simply apply to
&lt;code&gt;js-ts-mode&lt;&#x2F;code&gt; as well, without needing to write it explicitly. A nice quality of
life change to help pare down Emacs configurations that rely on both modes
(which is more common than you might think, given that non-tree-sitter modes are
typically more featureful).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;minibuffer-qol-improvements&quot;&gt;Minibuffer QOL improvements&lt;&#x2F;h2&gt;
&lt;p&gt;Some nice quality-of-life improvements for the default Emacs completions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You can now use the arrow keys to navigate the completion buffer vertically
(in addition to the &lt;code&gt;M-&amp;lt;up|down&amp;gt;&lt;&#x2F;code&gt; keybindings).&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Previous minibuffer completion selections are deselected when you begin typing
again (to avoid accidentally hitting a previous selection).&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;completions-sort&lt;&#x2F;code&gt; has a new value: &lt;code&gt;historical&lt;&#x2F;code&gt;. Completion candidates will
be sorted by their order in minibuffer history so that recent candidates
appear first.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;customize-interface-for-dir-locals&quot;&gt;Customize interface for dir-locals&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s now a customize interface for
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Directory-Variables.html&quot;&gt;Directory Variables&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M-x customize-dirlocals&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I always find myself forgetting the &lt;code&gt;.dir-locals.el&lt;&#x2F;code&gt; syntax (even though they&#x27;re
just lists!) so this is a surprisingly handy feature for me.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;new-mode-visual-wrap-prefix-mode&quot;&gt;New mode: visual-wrap-prefix-mode&lt;&#x2F;h2&gt;
&lt;p&gt;Now this one is cool. I&#x27;m the kind of guy who uses &lt;code&gt;auto-mode&lt;&#x2F;code&gt; for everything
because I haven&#x27;t bothered to figure out how Emacs line wrapping works.
Everything I write hard breaks into newlines after 80 characters.&lt;&#x2F;p&gt;
&lt;p&gt;The new mode &lt;code&gt;visual-wrap-prefix-mode&lt;&#x2F;code&gt; is like &lt;code&gt;auto-mode&lt;&#x2F;code&gt;, except that the
breaks are for display purposes only. I think this is incredibly useful when
editing text that might be reviewed using a diffing tool, since long lines tend
to display more useful diffs than a paragraph broken up with hard breaks. I&#x27;m
actually pretty excited about this change, maybe it will get me to stop using
&lt;code&gt;(markdown-mode . (( mode . auto-fill))&lt;&#x2F;code&gt; everywhere.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;new-command-replace-regexp-as-diff&quot;&gt;New command: replace-regexp-as-diff&lt;&#x2F;h2&gt;
&lt;p&gt;You can now visualize regular expression replacements as diffs before they&#x27;re
accepted. This is actually incredible.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;new-package-which-key&quot;&gt;New package: which-key&lt;&#x2F;h2&gt;
&lt;p&gt;Previously a package in GNU ELPA, &lt;code&gt;which-key-mode&lt;&#x2F;code&gt; is now built-in. With
&lt;code&gt;which-key-mode&lt;&#x2F;code&gt; enabled, after you begin a new command (e.g. &lt;code&gt;C-x&lt;&#x2F;code&gt;) and wait a
few seconds, a minibuffer will pop up with a list of possible keybinding
completions. It&#x27;s a super handy tool for remembering some of the more esoteric
modes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;new-customizations&quot;&gt;New customizations&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Show the current project (via &lt;code&gt;project.el&lt;&#x2F;code&gt;) in your modeline with
&lt;code&gt;project-mode-line&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Add right-aligned modeline elements via &lt;code&gt;mode-line-format-right-align&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;You can now customize the venerable &lt;code&gt;yes-or-no-p&lt;&#x2F;code&gt; function with
&lt;code&gt;yes-or-no-prompt&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;a-few-emacs-lisp-changes&quot;&gt;A few Emacs Lisp changes&lt;&#x2F;h2&gt;
&lt;p&gt;There are a few small, yet impactful changes around help buffers and Emacs Lisp
types that I think are worth noting.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;describe-function&lt;&#x2F;code&gt; shows the function inferred type when available:&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;C-h f concat RET&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(concat &amp;amp;rest SEQUENCES)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Type: (function (&amp;amp;rest sequence) string)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;Built-in types show their related classes:&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;C-h o integer RET&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;integer is a type (of kind ‘built-in-class’).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; Inherits from ‘number’, ‘integer-or-marker’.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; Children ‘fixnum’, ‘bignum’.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;The byte compiler warns if a file is missing the lexical binding directive.
Lexical bindings have been included in ELisp for awhile now, so it&#x27;s nice to
see more effort being made towards making it the default.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;; Foo mode  &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;-*-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; lexical-binding&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; -*-&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;read-the-full-details&quot;&gt;Read the full details&lt;&#x2F;h2&gt;
&lt;p&gt;That wraps up my highlights. There&#x27;s a ton more stuff included in Emacs 30.1 so
I encourage you to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.savannah.gnu.org&#x2F;cgit&#x2F;emacs.git&#x2F;tree&#x2F;etc&#x2F;NEWS?h=emacs-30&quot;&gt;check it out the NEWS yourself&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;PS. Interested in trying out Emacs but don&#x27;t know where to start? Check out my
MIT-licensed configuration guide:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&quot;&gt;Start Emacs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Recently</title>
        <published>2024-06-19T00:00:00+00:00</published>
        <updated>2024-06-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-06-19-recently/"/>
        <id>https://mgmarlow.com/words/2024-06-19-recently/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-06-19-recently/">&lt;p&gt;Recently I&#x27;ve been deep down a crossword puzzle rabbit hole. I started a new
side project that has taken most of my writing energy:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;txwords.com&quot;&gt;them&#x27;s crossing words&lt;&#x2F;a&gt;, a blog where I post daily
crossword puzzle reviews and articles about the craft of puzzle construction.
Thus far there&#x27;s about fifty crossword puzzles featured and discussed, a sizable
number of grids with over 10k words dedicated to crossing them.&lt;&#x2F;p&gt;
&lt;p&gt;When I started the project I thought I might burn out quickly on the idea.
Writing a daily review was actually far from my original intent. The thing is,
there&#x27;s just so much to talk about when it comes to the art of crossword
construction (and puzzles in general, by extension). Every crossword is nuanced
and interesting, built by constructors that bring their own voice into the grid
with interesting clues and clever themes.&lt;&#x2F;p&gt;
&lt;p&gt;The idea of a puzzle blog has been bouncing around in my head for a long time
now, and my motivation to start one was largely influenced by the release of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;499180&#x2F;Braid_Anniversary_Edition&#x2F;&quot;&gt;Braid, Anniversary Edition&lt;&#x2F;a&gt;.
When I was a kid playing through Braid on the Xbox Live Arcade, I didn&#x27;t
actually care that much for puzzle games. They were too slow and plodding for my
High School brain.&lt;&#x2F;p&gt;
&lt;p&gt;Since then I&#x27;ve come to really appreciate the genre, with games like The Witness
and Outer Wilds completely blowing my mind as to what&#x27;s possible in the medium
of video games. When Braid re-released this year with loads of developer
commentary, I was in.&lt;&#x2F;p&gt;
&lt;p&gt;Now that I&#x27;m playing through it as an adult I have a newfound appreciation for
its narrative and design. There are loads of spots where the narrative of the
tale is paralleled by the mechanics of the gameplay and the design of the
puzzles, a genius combination of factors seen in few games. What really drew me
in, however, was the developer commentary, discussing the minutiae of game,
sound, narrative, and art design behind every level and artistic motif.&lt;&#x2F;p&gt;
&lt;p&gt;The body of commentary in Braid, Anniversary Edition is staggering. The amount
of thought that bleeds into every ounce of that game is an incredible
achievement showing just how artistic the medium of video games can be. It
inspired me to start writing about puzzles because I think they&#x27;re more than
just a method of wasting time. They&#x27;re little worlds of simulation where ideas
mesh with action, a creative landscape of human ingenuity clashing with
constraints.&lt;&#x2F;p&gt;
&lt;p&gt;Needless to say I&#x27;ve been noodling on puzzles for the last few months, looking
back on some of my old &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;txwords.com&#x2F;games&#x2F;&quot;&gt;Puzzlescript prototypes&lt;&#x2F;a&gt; and
some of the things I learned about game design when I hacked them together.
Building a level for a puzzle game is kind of like mentoring new engineers. You
never really know how well you understand an idea until you need to teach it to
someone. Same goes for a mechanic in a puzzle game: what is the truth that
you&#x27;re trying to expose to the person playing your game? Why is it interesting?&lt;&#x2F;p&gt;
&lt;p&gt;Anyway, this is less of a Recently and more of a ramble.&lt;&#x2F;p&gt;
&lt;p&gt;For this blog, I&#x27;ve also been interested in exploring systems languages now that
I have a year or so of Rust experience under my belt. I&#x27;m curious about the idea
of manual memory management and how it is handled by various different
languages, especially with the recent uptick in C-alternatives, like Zig, Odin,
Jai, among others. I&#x27;ve also been wanting to do a post on &quot;learning systems
languages as a webdev&quot; for awhile now, exploring the Rust ecosystem as a Ruby on
Rails&#x2F;JavaScript developer. I think I need a stronger baseline in systems
performance before I try to tackle that subject.&lt;&#x2F;p&gt;
&lt;p&gt;Right now I&#x27;m reading through &lt;em&gt;A Tour of C++&lt;&#x2F;em&gt; and &lt;em&gt;Understanding Software
Dynamics&lt;&#x2F;em&gt;, testing my knowledge of performance programming and how computers
actually work under the hood. It&#x27;s been a humbling experience working in manual
memory scenarios after so many years of garbage-collected languages. It&#x27;s a
different landscape when you have to deal with certain hot-paths in a game loop,
for example, where allocations can lead to undesirable performance
characteristics.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Markdown Rendering with Awk</title>
        <published>2024-03-23T00:00:00+00:00</published>
        <updated>2024-03-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-03-23-markdown-awk/"/>
        <id>https://mgmarlow.com/words/2024-03-23-markdown-awk/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-03-23-markdown-awk/">&lt;p&gt;I can&#x27;t believe I&#x27;m writing
&lt;a href=&quot;&#x2F;words&#x2F;2024-02-27-awk-is-cool&#x2F;&quot;&gt;another post about Awk&lt;&#x2F;a&gt; but I&#x27;m just having too
much fun throwing together tiny Awk scripts. This time around that tiny Awk
script is a markdown renderer, converting markdown to good ol&#x27; HTML.&lt;&#x2F;p&gt;
&lt;p&gt;Markdown is interesting because 80% of the language is rather easy to implement
by walking through a file and applying a regular expression to each line. Most
implementations seem to start this way. However, once that final 20% is hit some
aspects of the language start to show their warts, exposing the not-so-happy
path that eventually leads to lexical analysis.&lt;&#x2F;p&gt;
&lt;p&gt;To list a few examples,&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;there are many elements that can span more than one line, like paragraph
emphasis, links, or bolded text&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;elements can encompass sub-elements like a russian doll, e.g. headers that
include emphasized text that itself is bolded&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;elements can defy existing behavior, like code blocks that can themselves
contain unrendered markdown&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Each of these conditions complicates the simple line-based approach.&lt;&#x2F;p&gt;
&lt;p&gt;The renderer that I&#x27;m building doesn&#x27;t aim to be comprehensive, so most of these
edge cases are not handled. For my toy renderer, I&#x27;m assuming that the markdown
is written in accordance to a general style guide with friendly linebreaks
between paragraphs.&lt;&#x2F;p&gt;
&lt;p&gt;I am also being careful to call this project an markdown &quot;renderer&quot; and not a
&quot;parser&quot; because it&#x27;s not &lt;em&gt;really&lt;&#x2F;em&gt; parsing the markdown file. Instead, we&#x27;re
immediately replacing markdown with HTML. The difference may seem nitpicky but
implies that there&#x27;s no intermediate format between the markdown and the HTML
output, a nuance that makes this implementation less powerful but also much
simpler.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s get cracking.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;initial-approach&quot;&gt;Initial approach&lt;&#x2F;h2&gt;
&lt;p&gt;Headers are a natural first step. The solution emphasizes Awk&#x27;s strengths when
it comes to handing line-based input:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# &#x2F; { print &amp;quot;&amp;lt;h1&amp;gt;&amp;quot; substr($0, 3) &amp;quot;&amp;lt;&#x2F;h1&amp;gt;&amp;quot; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This reads, &quot;On every line, match &lt;code&gt;#&lt;&#x2F;code&gt; followed by a space. Replace that line
with header tags and the text of that line beginning at index 3 (one-based
indexing).&quot; Since we&#x27;re piping &lt;code&gt;awk&lt;&#x2F;code&gt; into another file, &lt;code&gt;print&lt;&#x2F;code&gt; statements are
effectively writing our rendered HTML.&lt;&#x2F;p&gt;
&lt;p&gt;Line replacements are the name of the game in Awk, where the simplicity of the
syntax really shines. The call to &lt;code&gt;substr&lt;&#x2F;code&gt; is less elegant than the usual
space-delimited Awk fields (&lt;code&gt;$1&lt;&#x2F;code&gt;, &lt;code&gt;$2&lt;&#x2F;code&gt;, etc.), but it&#x27;s necessary since we want
to preserve the entire line sans the first two characters (the leading header
hashtag).&lt;&#x2F;p&gt;
&lt;p&gt;The remaining headers follow the same pattern:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# &#x2F;   { print &amp;quot;&amp;lt;h1&amp;gt;&amp;quot; substr($0, 3) &amp;quot;&amp;lt;&#x2F;h1&amp;gt;&amp;quot; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;## &#x2F;  { print &amp;quot;&amp;lt;h2&amp;gt;&amp;quot; substr($0, 4) &amp;quot;&amp;lt;&#x2F;h2&amp;gt;&amp;quot; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;### &#x2F; { print &amp;quot;&amp;lt;h3&amp;gt;&amp;quot; substr($0, 5) &amp;quot;&amp;lt;&#x2F;h3&amp;gt;&amp;quot; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For something a little trickier, let&#x27;s move on to block quotes. Block quotes in
Markdown look like the following, leading each line with a caret:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;&amp;gt; Deep in the human unconscious is a pervasive need for a logical universe that&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;&amp;gt; makes sense. But the real universe is always one step beyond logic. - Frank&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;&amp;gt; Herbert&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finding block quote lines is easy, we just use the same approach as our headers:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&amp;gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;blockquote&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; substr&lt;&#x2F;span&gt;&lt;span&gt;($0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;); &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;&#x2F;blockquote&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But as you have probably guessed, this simplification isn&#x27;t quite what we want.
Instead of wrapping each line with a block quote tag, we want to wrap the entire
block (three lines in this case) with one set of tags. This will require us to
keep track of some intermediate state between line-reads:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&amp;gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;inquote)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;blockquote&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; inquote &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; substr&lt;&#x2F;span&gt;&lt;span&gt;($0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If we match a blockquote character and we&#x27;re not yet &lt;code&gt;inquote&lt;&#x2F;code&gt;, we write the
opening tag and set &lt;code&gt;inquote&lt;&#x2F;code&gt;. Otherwise, we simply write the content of the
line. We need an extra rule to write the closing tag:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;inquote &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;amp;&amp;amp; !&#x2F;^&amp;gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;&#x2F;blockquote&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;; inquote &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If our program state says we&#x27;re in a quote but we reach a line that doesn&#x27;t lead
with a block quote character, it&#x27;s time to close the block. This matches against
paragraph breaks which are normally used to separate paragraphs in Markdown
documents.&lt;&#x2F;p&gt;
&lt;p&gt;This same strategy can be applied to the other block-style markdown elements:
code blocks and lists. Each requires its own variable to keep track of the block
content, but the approach is the same.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;paragraphs-are-tricky&quot;&gt;Paragraphs are tricky&lt;&#x2F;h2&gt;
&lt;p&gt;It is very tempting to implement inline paragraph elements in the same way as
we&#x27;ve handled other, single-line markdown syntax. However, paragraphs are
special in that they often span more than a single line, especially so if you
use hard-wrapping in your text editor at some column. For example, it&#x27;s very
common for links to span multiple lines:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;markdown&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;A link that spans &lt;&#x2F;span&gt;&lt;span style=&quot;color: #B4BEFE;&quot;&gt;[multiple lines]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;https:&#x2F;&#x2F;definitely-a-valid-link-here.com&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This breaks the nice, single-line worldview that we&#x27;ve been operating under,
requiring some special handling that will end up leaking into other aspects of
our rendering engine.&lt;&#x2F;p&gt;
&lt;p&gt;My approach is to collect multiple paragraph lines into a single string,
rendering it altogether on paragraph breaks. This allows me to search the entire
string for inline elements (links, bold, italics), effectively matching against
multiple lines of input.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; (i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span&gt;NF; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt;) collect($i) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt; { flushp() }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Concatenate our multi-line string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; collect&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;v&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  line &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; line sep v&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sep &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Flush the string, rendering any inline HTML elements&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; flushp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (line) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;p&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; render(line)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;&#x2F;p&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    line &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; sep &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each line of text is collected into a variable, &lt;code&gt;line&lt;&#x2F;code&gt;, that is persisted
between line-reads. When a paragraph break is hit (a line that contains no text,
&lt;code&gt;&#x2F;^$&#x2F;&lt;&#x2F;code&gt;) we render that line, wrapping it in paragraph tags and replacing any
inline elements with their respective HTML tags.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll point out that the technique of collecting fields into a string or array is
a very common pattern in Awk, hence the utility variable &lt;code&gt;NF&lt;&#x2F;code&gt; for &quot;number of
fields&quot;. The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;awk.dev&quot;&gt;Awk book&lt;&#x2F;a&gt; uses this pattern in quite a few
places.&lt;&#x2F;p&gt;
&lt;p&gt;For completeness, here&#x27;s what that render function looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; render&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt;(line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt;_(.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;)_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        gsub&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;_(.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;)_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; sprintf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&amp;lt;em&amp;gt;%s&amp;lt;&#x2F;em&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; substr&lt;&#x2F;span&gt;&lt;span&gt;(line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; RSTART&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; RLENGTH&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; line)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt;(line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;(.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;)\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        gsub&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;(.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt;)\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; sprintf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&amp;lt;strong&amp;gt;%s&amp;lt;&#x2F;strong&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; substr&lt;&#x2F;span&gt;&lt;span&gt;(line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; RSTART&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; RLENGTH&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; line)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt;(line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;\(.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;\)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inner &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; substr&lt;&#x2F;span&gt;&lt;span&gt;(line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; RSTART&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; RLENGTH&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        split&lt;&#x2F;span&gt;&lt;span&gt;(inner&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; spl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;\(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;        gsub&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;\(.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;\)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; sprintf&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&amp;lt;a href=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;%s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;gt;%s&amp;lt;&#x2F;a&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; spl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; spl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; line)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; line&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This code is noticeably less clean than our earlier HTML rendering, an
unfortunate consequence of handling multi-line paragraphs. I won&#x27;t go into too
much detail here since there&#x27;s a lot of Awk-specific regular expression matching
stuff going on, but the gist is a standard regexp-replace of the paragraph text
with HTML tags for matching elements.&lt;&#x2F;p&gt;
&lt;p&gt;Another problem that we run into when collecting multiple lines into the &lt;code&gt;line&lt;&#x2F;code&gt;
variable is accidentally collecting text from previous match rules. Awk&#x27;s
expression syntax is like a switch statement that lacks a break: a line will
match as many expressions as it can before moving onto the next. That means that
all of our previous rules for headers, blockquotes, and so on are now also
included in our paragraph text. That&#x27;s no good!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# I match a header here:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# &#x2F;  { print &amp;quot;&amp;lt;h1&amp;gt;&amp;quot; substr($0, 3) &amp;quot;&amp;lt;&#x2F;h1&amp;gt;&amp;quot; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# But I also match &amp;quot;any text&amp;quot; here, so I&amp;#39;m collected:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; (i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span&gt;NF; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt;) collect($i) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each of our previous matchers now has to include a call to &lt;code&gt;next&lt;&#x2F;code&gt; to immediately
stop processing and move on to the next line. This prevents them from being
included in paragraph collection.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# &#x2F;  { print &amp;quot;&amp;lt;h1&amp;gt;&amp;quot; substr($0, 3) &amp;quot;&amp;lt;&#x2F;h1&amp;gt;&amp;quot;; next }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;^&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;## &#x2F; { print &amp;quot;&amp;lt;h2&amp;gt;&amp;quot; substr($0, 4) &amp;quot;&amp;lt;&#x2F;h2&amp;gt;&amp;quot;; next }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;styling-for-html-exports&quot;&gt;Styling for HTML exports&lt;&#x2F;h2&gt;
&lt;p&gt;The last piece of this Markdown renderer is adding the boilerplate HTML that
wraps our document:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;!doctype html&amp;gt;&amp;lt;html&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;head&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;  &amp;lt;meta charset=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;utf-8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;  &amp;lt;meta name=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;viewport&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; content=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;width=device-width, initial-scale=1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (head)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span&gt; head&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;&#x2F;head&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;body&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ... all of our rules go here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;&#x2F;body&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&amp;lt;&#x2F;html&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Unlike other Awk matchers, the special &lt;code&gt;BEGIN&lt;&#x2F;code&gt; and &lt;code&gt;END&lt;&#x2F;code&gt; keywords are only
executed once.&lt;&#x2F;p&gt;
&lt;p&gt;As a nice bonus, we can add an optional &lt;code&gt;head&lt;&#x2F;code&gt; variable to inject a stylesheet
into our rendered markdown, which can be added via the Awk CLI. The following
adds the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;simplecss.org&#x2F;&quot;&gt;Simple CSS&lt;&#x2F;a&gt; stylesheet to our rendered output:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;awk -v head=&amp;#39;  &amp;amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https:&#x2F;&#x2F;cdn.simplecss.org&#x2F;simple.min.css&amp;quot;&amp;amp;gt;&amp;#39; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -f awkdown.awk README.md &amp;gt; docs&#x2F;index.html&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The full source code is available here: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;awkdown&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;awkdown&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>DM Tools with Awk</title>
        <published>2024-02-27T00:00:00+00:00</published>
        <updated>2024-02-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-02-27-awk-is-cool/"/>
        <id>https://mgmarlow.com/words/2024-02-27-awk-is-cool/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-02-27-awk-is-cool/">&lt;p&gt;I picked up Awk on a whim and am blown away by how generally useful it is. What
I thought was a quick and dirty tool for parsing tabulated files turns out to be
a fully-featured scripting language.&lt;&#x2F;p&gt;
&lt;p&gt;Before I started reading the second edition of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;awk.dev&#x2F;&quot;&gt;The Awk Programming Language&lt;&#x2F;a&gt;, my only exposure to Awk was
from better-minded folk on Stack Overflow. After copy-pasting a short script
here or there, I was befuddled by the need for explicit &lt;code&gt;BEGIN&lt;&#x2F;code&gt; and &lt;code&gt;END&lt;&#x2F;code&gt;
statements in Awk one-liners. Shouldn&#x27;t a program know when it begins and ends?
Why the redundancy?&lt;&#x2F;p&gt;
&lt;p&gt;Oh how wrong I was. Once you understand how Awk works, the syntax of &lt;code&gt;BEGIN&lt;&#x2F;code&gt; and
&lt;code&gt;END&lt;&#x2F;code&gt; makes a ton of sense; it&#x27;s actually a consequence of Awk&#x27;s coolest
feature. &lt;code&gt;BEGIN&lt;&#x2F;code&gt; and &lt;code&gt;END&lt;&#x2F;code&gt; are necessary because the default mode of an Awk
script isn&#x27;t top-to-bottom execution, like other scripting languages. Instead,
Awk programs are executed repeatedly by default, either on the lines of a file
or an input stream.&lt;&#x2F;p&gt;
&lt;p&gt;To demonstrate, say I have a file where each line contains a location:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Forest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Hills&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Desert&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I can use Awk to turn that list of locations into one that is numbered with a
single statement, no loops required:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ awk &amp;#39;{ print NR &amp;quot;. &amp;quot; $0 }&amp;#39; locations.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1. Forest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2. Hills&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;3. Desert&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;4. ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Without the &lt;code&gt;BEGIN&lt;&#x2F;code&gt; or &lt;code&gt;END&lt;&#x2F;code&gt; markers (which denote &quot;run this before&quot; and &quot;run
this after&quot;), Awk runs statements on every line of its input. In this case, that
means re-printing each location in the file &lt;code&gt;locations.txt&lt;&#x2F;code&gt; with some minor
modifications.&lt;&#x2F;p&gt;
&lt;p&gt;Awk provides a bunch of built-ins that make it easy to work within this
execution model. &lt;code&gt;NR&lt;&#x2F;code&gt; refers to &quot;num row&quot;, keeping track of the current line of
input that is being processed. This generates our numbered list.&lt;&#x2F;p&gt;
&lt;p&gt;The dollar-sign variables refer to fields on an individual line. &lt;code&gt;$0&lt;&#x2F;code&gt; is the
entire line, unmodified. &lt;code&gt;$1&lt;&#x2F;code&gt;, &lt;code&gt;$2&lt;&#x2F;code&gt;, and so on refer to subsets of the line,
broken up by a delimiter (e.g. space, tab, or comma) and read from left to
right.&lt;&#x2F;p&gt;
&lt;p&gt;And statements are just the tip of the Awk iceberg! You can assign each
statement a &quot;matcher&quot; that only runs the expression on lines that are truthy.
Here are a few examples:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Print every row but the first&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;NR&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span&gt; $0 }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Only print a row if the first field matches &amp;quot;cat&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ~ &#x2F;&lt;&#x2F;span&gt;&lt;span&gt;cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;not a dog&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Maybe your second field is a number?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt; $2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 18&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;teenager&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the &lt;code&gt;BEGIN&lt;&#x2F;code&gt; and &lt;code&gt;END&lt;&#x2F;code&gt; statements are starting to make more sense.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dming-with-awk&quot;&gt;DMing with Awk&lt;&#x2F;h2&gt;
&lt;p&gt;Now for something a little more complicated. As I mentioned before, Awk is a
fully-featured scripting language. You can write functions, generate random
numbers, build arrays, and do everything that you&#x27;d expect a normal language to
do (mostly, anyway). I ran across an example in the Awk book that demonstrates
the use of &lt;code&gt;rand()&lt;&#x2F;code&gt; via dice rolling and it sparked an idea: how useful can a
tool like Awk be for a DM running a Dungeons and Dragons game?&lt;&#x2F;p&gt;
&lt;p&gt;Since Awk is great at reading files, I figured it would also be great for
dealing with random tables. Given the locations file that appears earlier in
this post, here&#x27;s how you can select a single location at random:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;awk&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;{data[NR] = $0} END {srand(); print data[int(rand()*length(data))]}&amp;#39; locations.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s easier to read with some annotations:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# Add every line in the file to an array, indexed by the line number&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{ data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;NR&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span&gt; $0 }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# After reading the file,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # Seed randomness&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  srand&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # Pick a random index from the data array and print its respective value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  print&lt;&#x2F;span&gt;&lt;span&gt; data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rand&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; length&lt;&#x2F;span&gt;&lt;span&gt;(data))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I really like how &lt;code&gt;{ data[NR] = $0 }&lt;&#x2F;code&gt; is all that Awk needs to build an array
with the contents of a file. It comes in handy in cases like this where we need
the file contents in memory before we can do something useful.&lt;&#x2F;p&gt;
&lt;p&gt;Now, you might be thinking that this isn&#x27;t that cool because &lt;code&gt;sort&lt;&#x2F;code&gt; can already
do it better. And you&#x27;d be right!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; cat locations.txt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; sort&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -R&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;Plains&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So how about moving on to the next step instead: character generation. The next
script implements the charater creation rules from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;questingbeast.itch.io&#x2F;knave&quot;&gt;Knave&lt;&#x2F;a&gt;, a game based on old-school
Dungeons and Dragons.&lt;&#x2F;p&gt;
&lt;p&gt;The first thing we need to do is generate some attribute scores. Each score can
be simulated by rolling three 6-sided dice (d6) and taking the lowest result.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;BEGIN&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;    srand&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;str&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;dex&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;con&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;int&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;wis&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;cha&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;hp &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; roll(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;8&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;; i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 6&lt;&#x2F;span&gt;&lt;span&gt;; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt; map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; lowest_3d6()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; roll&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; int&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rand&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; n)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; lowest_3d6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;_i&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;_tmp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    min &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; roll(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; (_i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;; _i &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;; _i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;++&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        _tmp &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; roll(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;6&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; (_tmp &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; min) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            min &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; _tmp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; min&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The output looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ awk -f knave.awk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hp 6&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;str 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dex 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;con 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;int 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wis 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cha 4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since this Awk program is not reading from a file (yet), everything is run in a
&lt;code&gt;BEGIN&lt;&#x2F;code&gt; block. This allows us to execute Awk without passing in a file or input
stream. Within that &lt;code&gt;BEGIN&lt;&#x2F;code&gt; block we build a map of integers to attribute names,
making it easy to loop over them to roll for scores. Arrays in Awk are
association lists, so they work well for this use-case.&lt;&#x2F;p&gt;
&lt;p&gt;The strange thing about this code is the use of parameters as local variables in
the function &lt;code&gt;lowest_3d6&lt;&#x2F;code&gt;. The only way in Awk to make a variable local is to
provide it to the parameter list when declaring a function, as all other
variables are global. Idiomatic Awk attempts to reveal this strangeness by
adding an underscore to the parameter names, as I have done, or by inserting a
bunch of spaces before their place in the function definition.&lt;&#x2F;p&gt;
&lt;p&gt;Next up is to make these characters more interesting by assigning them careers
and starting items. A career describes the character&#x27;s origin, explaining their
initial loot as fitting to their backstory. These careers are taken from Knave
second edition.&lt;&#x2F;p&gt;
&lt;p&gt;First, a new data file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;acolyte: candlestick, censer, incense&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jailer: padlock, 10’ chain, wine jug&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;acrobat: flash powder, balls, lamp oil&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jester: scepter, donkey head, motley&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;actor: wig, makeup, costume&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jeweler: pliers, loupe, tweezers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now that our Awk program is reading lines from a file, we can add a new block
that stores careers into an array so we can make a random selection for the
player.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;awk&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ...snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{ careers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;NR&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;] =&lt;&#x2F;span&gt;&lt;span&gt; $0 }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;END&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;Career &amp;amp; items:&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt; careers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;roll(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When the program is executed with the list of careers, the output looks like
this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ awk -f knave.awk careers.txt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hp 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;str 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dex 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;con 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;int 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wis 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cha 4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Career &amp;amp; items:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;falconer: bird cage, gloves, whistle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not bad!&lt;&#x2F;p&gt;
&lt;p&gt;I doubt these tools will come in handy for your next DnD campaign, but I hope
that this post has inspired you to pick up Awk and give it a go on some
unconventional problems.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A Few Months with Kagi</title>
        <published>2024-02-06T00:00:00+00:00</published>
        <updated>2024-02-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-02-06-few-months-with-kagi/"/>
        <id>https://mgmarlow.com/words/2024-02-06-few-months-with-kagi/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-02-06-few-months-with-kagi/">&lt;p&gt;I&#x27;ve subscribed to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kagi.com&#x2F;&quot;&gt;Kagi&lt;&#x2F;a&gt; for a few months now and wanted to
collect some of my feelings towards it, particularly addressing whether I think
it&#x27;s worth paying $10&#x2F;mo for a search engine. To summarize: yes, I do think Kagi
is worth the price. Kagi performs as good or better than the competition for the
majority of things I search (programming stuff and a healthy dose of Emacs) and
offers a few useful features that add to the experience. The fact that it
performs so well without abusing my personal data is quite the achievement.&lt;&#x2F;p&gt;
&lt;p&gt;The thing that got me into Kagi in the first place was their post about
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.kagi.com&#x2F;small-web&quot;&gt;Kagi Small Web&lt;&#x2F;a&gt;. It shouldn&#x27;t really be
surprising that I am passionate about RSS and self-hosted blogs, you&#x27;re reading
one. What Kagi is doing to help feature small blogs in their search results is
commendable. It clearly demonstrates the niche that Kagi is trying to carve out
as a search engine in a space owned by giants: the search experience provided by
Google is plagued with incentives that make the web worse and the problems of
such incentives are rooted in ads and SEO.&lt;&#x2F;p&gt;
&lt;p&gt;Whether or not you&#x27;re willing to pay for Kagi essentially boils down to whether
you think search is important enough to justify the bill, with a couple caveats.
The first caveat is considering what your personal data is worth to you. The
second is whether you think paying for a service subscription is a better
business model than paying with your eyeballs on a free service that serves ads.&lt;&#x2F;p&gt;
&lt;p&gt;To help you figure out how Kagi performs, the rest of this post provides some of
my takeaways for day-to-day features. To put things into perspective, I average
about 1k searches a month and use Kagi across all of my devices: home pc, work
pc, and phone.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;search-results&quot;&gt;Search results&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s start with the search results. The vast majority of my searches are
programming-related, either APIs in programming languages, APIs in third-party
libraries, or documentation resources and higher-level ideas. In each of these
categories I find Kagi provides really excellent results, particularly when
compared to my previous search engine, DuckDuckGo (DDG).&lt;&#x2F;p&gt;
&lt;p&gt;My DDG use can be summarized by one symbol: &lt;code&gt;!g&lt;&#x2F;code&gt;. I had to constantly redirect
my searches to Google to get good results on the first page. DDG works fine for
general searches (&quot;what&#x27;s this word?&quot;, &quot;who&#x27;s in this movie?&quot;) but I had a
really hard time using it when doing anything technical. With Kagi, I basically
never use &lt;code&gt;!g&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s hard to judge &quot;quality of search&quot; without exhaustively comparing results
for certain queries across different search engines, which I am not going to do.
Generally speaking, I&#x27;m impressed by Kagi&#x27;s highest-priority search results.
There seem to be fewer blog-spam articles from sites like Medium or dev.to
(which you can further exclude, see:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;help.kagi.com&#x2F;kagi&#x2F;features&#x2F;website-info-personalized-results.html#personalized-results&quot;&gt;personalized results&lt;&#x2F;a&gt;)
and more self-hosted blogs, Stack Overflow posts, and Github discussions&#x2F;issues.
I am often surprised by the quality of Github content in Kagi, as many of my
queries have been answered by a comment in a Github discussion or an issue that
is near the top of the results page.&lt;&#x2F;p&gt;
&lt;p&gt;By far my favorite aspect of Kagi search results is the prevalence of source
code links. For example, searching &quot;blank? rails&quot; turns up this link to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rails&#x2F;rails&#x2F;blob&#x2F;main&#x2F;activesupport&#x2F;lib&#x2F;active_support&#x2F;core_ext&#x2F;object&#x2F;blank.rb&quot;&gt;the Github source&lt;&#x2F;a&gt;
as the third result.&lt;&#x2F;p&gt;
&lt;p&gt;These source code links are so insanely useful. Normally I&#x27;d have to (a) search
the library (b) go to the library website (c) try to find the Github link (d)
use Github search to find the relevant file. Kagi bakes all of those steps into
one. I often use Kagi to search for a file that I know exists in some Github
repository, like &quot;autorun minitest&quot; and follow the Kagi result rather than going
to Github and using its navigation features.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;personalized-results&quot;&gt;Personalized results&lt;&#x2F;h2&gt;
&lt;p&gt;I mentioned this in the previous section, but you can &quot;raise&quot;, &quot;lower&quot;, or
&quot;block&quot; certain domains to affect their results in your search output. It&#x27;s not
totally clear to me how significant this setting is, but I use it to prioritize
&lt;code&gt;gnu.org&lt;&#x2F;code&gt; documentation for Emacs searches and lower Medium or &lt;code&gt;dev.to&lt;&#x2F;code&gt; results
to avoid blogspam. I have noticed that lowered results often sit at the bottom
of the page, so if you&#x27;re a fan of not seeing any Medium articles or Quora
questions in your top-most results it&#x27;s a very nice feature.&lt;&#x2F;p&gt;
&lt;p&gt;Kagi also hosts a public &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kagi.com&#x2F;stats?stat=leaderboard&quot;&gt;leaderboard&lt;&#x2F;a&gt;
that shows you how other people use this tool.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lenses&quot;&gt;Lenses&lt;&#x2F;h2&gt;
&lt;p&gt;Lenses are a Kagi feature that I thought I would never use, but given the right
application they&#x27;re actually pretty helpful. A lens effectively limits search
results to a particular domain. For example, a Hacker News lens would restrict
results to sites with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&quot;&gt;https:&#x2F;&#x2F;news.ycombinator.com&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I actually use that Hacker News lens frequently to track down articles that I
remember seeing, but don&#x27;t exactly remember the author or title. I can provide
some vague search terms about the content in the article, filter by my Hacker
News lens, and quickly look through a bunch of submissions that fit the
criteria. I find that the Kagi lens performs better for this specific use-case
than the Hacker News search on the website.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;things-kagi-does-not-get-right&quot;&gt;Things kagi does not get right&lt;&#x2F;h2&gt;
&lt;p&gt;Image results are good for the first few columns, but very quickly devolve into
complete nonsense. It&#x27;s actually hilarious how off-the-rails image searches can
get with Kagi. I&#x27;ve shared my results with friends quite a few times because the
results were so outlandish when compared to the search term that it made for
good comedy. This has improved significantly with a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kagifeedback.org&#x2F;d&#x2F;2793-dec-28-2023-improved-search-results-and-new-extension-for-safari&quot;&gt;recent update&lt;&#x2F;a&gt;
but I still go to another source when searching images.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;help.kagi.com&#x2F;kagi&#x2F;api&#x2F;summarizer.html&quot;&gt;universal summarizer&lt;&#x2F;a&gt; is a
neat idea but is untrustworthy in very subtle ways. I primarily use it when
summarizing Wikipedia articles with the shorthand flow: first search Wikipedia
(&quot;!w dynamic programming&quot;), then summarize the link (&quot;!sum
https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Dynamic_programming&quot;). Like all LLMs, the
summarizer sounds authoritative but frequently fabricates ideas or misrepresents
certain pieces of information. Perhaps this is less of a critique of Kagi and
more of LLMs in general, but I have barely touched this feature as a result.&lt;&#x2F;p&gt;
&lt;p&gt;Uptime for Kagi has been generally good, but last month there was a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;status.kagi.com&#x2F;clrnl9zwl97290beoine8zlvzx&quot;&gt;five-hour outage&lt;&#x2F;a&gt; that was
the source of much hand-wringing and consternation. If anything, this outage
demonstrated to me how much I prefer Kagi to other search engines. The
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;status.kagi.com&#x2F;clrnl9zwl97290beoine8zlvzx&quot;&gt;postmortem&lt;&#x2F;a&gt; is worth a
read.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;should-you-use-kagi&quot;&gt;Should you use Kagi?&lt;&#x2F;h2&gt;
&lt;p&gt;Yes, give it a go! Kagi outlines
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;help.kagi.com&#x2F;kagi&#x2F;why-kagi&#x2F;why-pay-for-search.html&quot;&gt;why you might want to pay for search&lt;&#x2F;a&gt;
rather eloquently in its documentation. I echo the philosophical argument and
add that the product itself is compelling.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My Year in Reading</title>
        <published>2024-01-07T00:00:00+00:00</published>
        <updated>2024-01-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2024-01-07-year-in-reading/"/>
        <id>https://mgmarlow.com/words/2024-01-07-year-in-reading/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2024-01-07-year-in-reading/">&lt;p&gt;I read 24 books in 2023, a total of just over 8,000 pages. It was an uncommonly
productive year for me, not driven by any particular goal but rather a general
interest in a few choice authors and compelling works. Here are some of the
highlights.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;murakami&quot;&gt;Murakami&lt;&#x2F;h2&gt;
&lt;p&gt;I read four different books by Haruki Murakami this year, starting with &lt;em&gt;What I
Talk About When I Talk About Running&lt;&#x2F;em&gt; and following up with &lt;em&gt;Hard-Boiled
Wonderland and the End of the World&lt;&#x2F;em&gt;, &lt;em&gt;Men Without Women&lt;&#x2F;em&gt;, and &lt;em&gt;The Wind-up Bird
Chronicle&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This Murakami binge all started from a friend&#x27;s recommendation. Before 2023 I
was totally unacquainted with Murakami&#x27;s work, although nowadays I the
ever-growing Murakami section in my local bookstore has me wondering how I
missed it. Beginning with a memoir (&lt;em&gt;What I Talk About&lt;&#x2F;em&gt;) is perhaps a strange
way to meet a new author, offering insights into the mind behind books that I&#x27;ve
not yet encountered. At least with Murakami&#x27;s memoir the subject stays focused
on his running and life philosophy, it doesn&#x27;t delve far into the subject matter
of his stories.&lt;&#x2F;p&gt;
&lt;p&gt;Something about the tone of &lt;em&gt;What I Talk About&lt;&#x2F;em&gt; caught me right away: a
relatively simple form of prose packed with emotion and relatability. This
feeling translates into all of Muarakami&#x27;s writing and is probably the core
reason I find his work so engaging. I struggle to put into words my reasons for
recommending his work to others, other than I think people should try it and
find for themselves whether it holds any meaning in their lives.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Hard-Boiled Wonderland&lt;&#x2F;em&gt; strikes me as one of his tighter novels, at least in
terms of a plot through-line and characters that grow together with the
protagonist. It&#x27;s an interesting take on a dual narrative, two parallel worlds
representing the &quot;Hard Boiled&quot; detective side and the &quot;End of the World&quot; dream
side. Although I didn&#x27;t care much for the characters themselves, the themes and
existential explorations were fun to explore.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Men Without Women&lt;&#x2F;em&gt; is a short story collection that I purposefully read before
watching the movie &lt;em&gt;Drive My Car&lt;&#x2F;em&gt;, which itself is based on several of the
stories in the book. The stories are interesting, engaging, and dreamy. Unlike a
full-length novel, a short story doesn&#x27;t need to work double-time to justify the
existence and evolution of its characters. The constraints of a short story work
in concert with Muarakami&#x27;s style and I think offer a more enjoyable reading
experience. This is probably the best place to start for new Murakami readers.&lt;&#x2F;p&gt;
&lt;p&gt;The final Murakami novel that I read was &lt;em&gt;The Wind-up Bird Chronicle&lt;&#x2F;em&gt;, which I
had described to me as his magnum opus. Although I finished it more than two
months ago I am still trying to sort out my feelings towards it. It has stuck in
the back of my mind in a way that few novels ever have, and I find myself
dwelling on certain scenes and characters to connect their meaning to events in
my own life. It&#x27;s a bizarre book that I can&#x27;t necessarily recommend, even though
I find it endlessly fascinating.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;doom&quot;&gt;Doom&lt;&#x2F;h2&gt;
&lt;p&gt;John Romero released an autobiography this year, &lt;em&gt;Doom Guy: Life in First
Person&lt;&#x2F;em&gt;, which despite its title (I mean, come on, really?) manages to be an
engaging glimpse into his life and one of the most important periods in video
game history.&lt;&#x2F;p&gt;
&lt;p&gt;There are now two books (that I know of) covering the saga of id Software:
&lt;em&gt;Masters of Doom&lt;&#x2F;em&gt;, probably one of the most famous books written about video
games, and Romero&#x27;s autobiography. I would not recommend Romero&#x27;s &lt;em&gt;Doom Guy&lt;&#x2F;em&gt;
over &lt;em&gt;Masters of Doom&lt;&#x2F;em&gt;, as the latter does a much better job developing the
behind-the-scenes drama and pacing itself as a proper story. However, &lt;em&gt;Doom Guy&lt;&#x2F;em&gt;
is still a worthy read for Doom-heads like me, as it provides lots of additional
insight into Romero&#x27;s evolution as a game designer.&lt;&#x2F;p&gt;
&lt;p&gt;Doom is still going strong, 2023 marking its 30 year anniversary. Following
&lt;em&gt;Doom Guy&lt;&#x2F;em&gt; I went down a pretty deep modding rabbit hole, exploring some of the
incredible stuff that people are making for this game.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.doomworld.com&#x2F;forum&#x2F;topic&#x2F;134292-myhousewad&#x2F;&quot;&gt;MyHouse.wad&lt;&#x2F;a&gt; in
particular is an astounding achievement that is worth checking out, at the very
least in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=5wAo54DHDY0&quot;&gt;Youtube form&lt;&#x2F;a&gt;. Romero
himself is still churning out new levels in the form of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;romero.com&#x2F;sigil&quot;&gt;Sigil&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-left-hand-of-darkness&quot;&gt;The Left Hand of Darkness&lt;&#x2F;h2&gt;
&lt;p&gt;Ursula K. Le Guin is quickly becoming one of my favorite Sci-fi authors. Last
year &lt;em&gt;The Dispossessed&lt;&#x2F;em&gt; gripped me with its prose, use of metaphor, and
occasional Taoist reference; I wrote a little bit about my reading experience
[here]({{
&#x27;&#x2F;words&#x2F;2022-08-13-reading-the-dispossessed.md&#x27; | url }}). &lt;em&gt;The Left Hand of
Darkness&lt;&#x2F;em&gt; is a very different story that encompasses many of the things I
enjoyed about &lt;em&gt;The Dispossessed&lt;&#x2F;em&gt;, but also delivers such complexity in its alien
culture that it&#x27;s unbelievable to me that it&#x27;s only 400 pages. The
world-building is so sincere and empathetic it&#x27;s hard to believe that Le Guin
didn&#x27;t travel to these planets and interview her characters personally.&lt;&#x2F;p&gt;
&lt;p&gt;While writing this post I&#x27;ve also finished &lt;em&gt;Under the Lathe of Heaven&lt;&#x2F;em&gt;, which to
me reads more like Philip K. Dick than Le Guin. The reason I bring up this point
is to contrast &lt;em&gt;The Left Hand of Darkness&lt;&#x2F;em&gt;, which I think delivers a style so
personal and unique to Le Guin that I can&#x27;t help believing it will be my
favorite work of hers for a long time.&lt;&#x2F;p&gt;
&lt;p&gt;I can&#x27;t say enough good things about this book!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lisping&quot;&gt;Lisping&lt;&#x2F;h2&gt;
&lt;p&gt;The percentage of non-fiction that I read is much smaller than fiction, both
because I think fiction is more important to read from a learning and
development standpoint (a view that I think differs from the norm) and because
fiction is more interesting. When I read non-fiction it&#x27;s almost always a book
about programming, something directly related to my career and skill
development.&lt;&#x2F;p&gt;
&lt;p&gt;Readers of this blog may have guessed, and I confirm, that my Emacs obsession
has likewise hijacked my non-fiction life. Emacs, it seems, is the gateway drug
to an obsession with parentheses, one that inevitably leads down the Common Lisp
rabbit hole, into a place where programmers are trapped in the 80s but at least
have lots of free and venerable literature to choose from.&lt;&#x2F;p&gt;
&lt;p&gt;One of such books is Paul Graham&#x27;s &lt;em&gt;On Lisp&lt;&#x2F;em&gt;, a Common Lisp book that focuses on
bottom-up programming and the power of macros. It&#x27;s the first Common Lisp book
that I&#x27;ve worked through that isn&#x27;t exclusively about the language (like
&lt;em&gt;Practical Common Lisp&lt;&#x2F;em&gt;, also excellent) but factors in higher-level ideas. &lt;em&gt;On
Lisp&lt;&#x2F;em&gt; is widely regarded as the &quot;macro explainer&quot; because it does an excellent
job demonstrating the power of Lisp macros during the early periods of
application architecture. I can likewise agree that &lt;em&gt;On Lisp&lt;&#x2F;em&gt; has given me a
greater appreciation for macros and metaprogramming, as well as a great respect
for the parentheses that rule Lisp-like languages.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to Install React</title>
        <published>2023-11-17T00:00:00+00:00</published>
        <updated>2023-11-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-11-15-how-to-install-react/"/>
        <id>https://mgmarlow.com/words/2023-11-15-how-to-install-react/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-11-15-how-to-install-react/">&lt;p&gt;Tsoding&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;XAGCULPO_DE&quot;&gt;recent stream about React&lt;&#x2F;a&gt; is a
hilarious reminder of the complications of web development tooling and the lack
of support for beginners who want to take a bottom-up approach to learning.&lt;&#x2F;p&gt;
&lt;p&gt;What I found most surprising about his foray into React is actually the React
documentation itself. It fails to provide any detail into setting up a React
build environment yourself, instead recommending frameworks and tools that hide
away the build mechanisms such that they&#x27;re entirely opaque to the developer.
For those who want to learn how things work from start to finish there&#x27;s not
much path forward, and it is all too easy to stumble onto old tools and
processes that have been left to decompose after their fifteen minutes of fame.&lt;&#x2F;p&gt;
&lt;p&gt;For example, the documentation for
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;react.dev&#x2F;learn&#x2F;start-a-new-react-project&quot;&gt;Start a New React Project&lt;&#x2F;a&gt;
points developers towards Next.js or Remix, frameworks that I view as highly
specialized and SSR-first. I can&#x27;t believe that this is an acceptable starting
point for a complete beginner, not only is React super complicated in and of
itself, but to have to learn the abstractions of a meta-framework on top of
that? Sounds like a nightmare! The
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;remix.run&#x2F;docs&#x2F;en&#x2F;main&quot;&gt;Remix documentation&lt;&#x2F;a&gt; alone offers a staggering
number of APIs. Where does React end and the framework begin? How is a beginner
supposed to know the difference?&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s also rather humorous how far React has moved away from its original goal of
being
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;web.archive.org&#x2F;web&#x2F;20171228052523&#x2F;https:&#x2F;&#x2F;reactjs.org&#x2F;&quot;&gt;&quot;A JavaScript library for building user interfaces.&quot;&lt;&#x2F;a&gt;.
I know SSR is the bee&#x27;s knees these days but I don&#x27;t think it should be
emphasized over the fundamental build processes that make the whole thing work.&lt;&#x2F;p&gt;
&lt;p&gt;In the days of yore, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;create-react-app.dev&#x2F;&quot;&gt;create-react-app&lt;&#x2F;a&gt; was
closer to this vision: run this CLI and bootstrap a React project that is just a
regular React single page application. Nowadays &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;vitejs.dev&#x2F;&quot;&gt;Vite&lt;&#x2F;a&gt; has
effectively superseded this project, and its omission in the React documentation
is surprising. Regardless, both of these projects still hide the details of the
React build process behind a velvet curtain.&lt;&#x2F;p&gt;
&lt;p&gt;I do think it&#x27;s important for beginners to familiarize themselves with some of
the fundamental tools that drive their favorite frameworks. The new kids on the
block like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;esbuild.github.io&#x2F;&quot;&gt;esbuild&lt;&#x2F;a&gt; are super approachable and
don&#x27;t involve setting up convoluted Webpack configs or plugin pipelines. The
rest of this post will demonstrate setting up a new React project with esbuild.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-esbuild&quot;&gt;Why esbuild?&lt;&#x2F;h2&gt;
&lt;p&gt;Why do we even need a build system in the first place? The answer is explained
in more detail in the next section, but the primary reason is to support
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;react.dev&#x2F;learn&#x2F;writing-markup-with-jsx&quot;&gt;React&#x27;s JSX syntax&lt;&#x2F;a&gt;. Browsers
don&#x27;t know what JSX is, so we convert it to JavaScript beforehand.&lt;&#x2F;p&gt;
&lt;p&gt;Before esbuild, most developers were transforming JSX via the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;babeljs.io&#x2F;docs&#x2F;babel-plugin-transform-react-jsx&quot;&gt;Babel JSX transform plugin&lt;&#x2F;a&gt;
together with a Webpack configuration that handled templating, static files, and
other optimizations. Now you could go and install Babel manually, like Tsoding
did in his stream, and use that instead for your new React project. However,
Babel is both more complicated and slower than esbuild, so I don&#x27;t think it&#x27;s a
good entrypoint for beginners.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;evanw&#x2F;esbuild&quot;&gt;esbuild compiler source code&lt;&#x2F;a&gt; is also a
great read.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-to-actually-install-react&quot;&gt;How to actually install React&lt;&#x2F;h2&gt;
&lt;p&gt;Start by initializing a new npm project:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;mkdir&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; react-hello-world&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; react-hello-world&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;npm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; init -y&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then add your dependencies, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.npmjs.com&#x2F;package&#x2F;react&quot;&gt;React&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.npmjs.com&#x2F;package&#x2F;react-dom&quot;&gt;ReactDOM&lt;&#x2F;a&gt;, and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.npmjs.com&#x2F;package&#x2F;esbuild&quot;&gt;esbuild&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why are &lt;code&gt;react&lt;&#x2F;code&gt; and &lt;code&gt;react-dom&lt;&#x2F;code&gt; different packages? I can only guess at the
intentions of the React core team, but my assumption is react-dom and react
are separate packages to enable some of the non-browser React targets, like
React Native. By shipping them in two packages, you can keep using &lt;code&gt;react&lt;&#x2F;code&gt; for
your application&#x27;s component&#x2F;UI code, and easily swap in the
&lt;code&gt;react-dom&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;react-native&lt;&#x2F;code&gt; entrypoints depending on whether you&#x27;re building a
web or mobile application. Since we&#x27;re working with React in a browser, we&#x27;ll
need ReactDOM to configure the library to the DOM.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;npm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; install react react-dom --save&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;npm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; install esbuild --save-dev&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We use &lt;code&gt;--save&lt;&#x2F;code&gt; on React and ReactDOM because they&#x27;re application dependencies,
things that our application runtime actually depends on. We use &lt;code&gt;--save-dev&lt;&#x2F;code&gt; for
esbuild since it&#x27;s a development dependency, it&#x27;s only needed to compile our
application. This is a bit of a pointless distinction at this point, since we&#x27;re
going to compile our application into a static JS file anyway, but hey, best
practice.&lt;&#x2F;p&gt;
&lt;p&gt;There are two pieces of boilerplate that we need to fill in next, the
&lt;code&gt;index.html&lt;&#x2F;code&gt; webpage where our JavaScript is loaded, and &lt;code&gt;index.jsx&lt;&#x2F;code&gt; which
contains the contents and initialization of our React application.&lt;&#x2F;p&gt;
&lt;p&gt;First &lt;code&gt;index.jsx&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; createRoot&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;react-dom&#x2F;client&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;hello react&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span&gt; root&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; createRoot&lt;&#x2F;span&gt;&lt;span&gt;(document&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;app&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;root&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;render&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then &lt;code&gt;index.html&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;&amp;lt;!doctype&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; lang&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;en&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;meta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; charset&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;meta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;viewport&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;meta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; http-equiv&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;X-UA-Compatible&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;ie=edge&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Hello, React&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;head&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;app&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; src&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;.&#x2F;out.js&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;script&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;html&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The important bit to note here is that the script tag in &lt;code&gt;index.html&lt;&#x2F;code&gt; is
pointing to a file that is yet to exist: &lt;code&gt;out.js&lt;&#x2F;code&gt;. Normally JS doesn&#x27;t require a
build step (although when shipping to production it is a good idea for asset
minification, among other reasons), but React is a special case because of the
JSX syntax. JSX is not part of the ECMAScript specification and is therefore not
known to the browser, so we have to add support for it by compiling our JSX code
into plain old JS (hence esbuild).&lt;&#x2F;p&gt;
&lt;p&gt;In versions of React before the automatic runtime (which I&#x27;ll get into in a
second), JSX compilation looked something like the following.&lt;&#x2F;p&gt;
&lt;p&gt;This JSX:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; original.jsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; React&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;react&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Hello World&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Is compiled to this JS:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; compiled.js&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; React&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;react&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span&gt; React&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;createElement&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;h1&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; null&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;Hello world&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In fact, you could write your entire app using the &lt;code&gt;React.createElement&lt;&#x2F;code&gt; API and
avoid the compilation step (although in practice JSX is really why you&#x27;re here
anyway).&lt;&#x2F;p&gt;
&lt;p&gt;Nowadays the compilation situation is a little more complicated. Programmers
found that repeating the &lt;code&gt;import React from &quot;react&quot;;&lt;&#x2F;code&gt; line at the top of every
JSX file was onerous, so the ecosystem grew to support a JSX syntax that doesn&#x27;t
require it. This is known as
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;legacy.reactjs.org&#x2F;blog&#x2F;2020&#x2F;09&#x2F;22&#x2F;introducing-the-new-jsx-transform.html&quot;&gt;the automatic JSX runtime&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Modern JSX is written without the import statement, like so:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; original.jsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt;Hello World&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;h1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the compiled JS output is likewise changed to support this new syntax:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; compiled.js&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; jsx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; _jsx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;react&#x2F;jsx-runtime&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; App&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; _jsx&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;h1&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;, {&lt;&#x2F;span&gt;&lt;span&gt; children&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;Hello world&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;react&#x2F;jsx-runtime&lt;&#x2F;code&gt; is now imported as the responsibility of the compiler,
not the programmer.&lt;&#x2F;p&gt;
&lt;p&gt;All this to say that we&#x27;ll need to enable the automatic JSX runtime when we
compile our &lt;code&gt;index.jsx&lt;&#x2F;code&gt; script into &lt;code&gt;out.js&lt;&#x2F;code&gt;. Luckily esbuild supports this out
of the box.&lt;&#x2F;p&gt;
&lt;p&gt;Add the following build script to your &lt;code&gt;package.json&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;scripts&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;quot;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;esbuild index.jsx --jsx=automatic --bundle --outfile=out.js&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The other point of note is the &lt;code&gt;--bundle&lt;&#x2F;code&gt; flag which inlines all of the
dependencies needed for the application during the build. For our application,
that means esbuild will insert React and ReactDOM from their respective
&lt;code&gt;node_modules&#x2F;&lt;&#x2F;code&gt; into &lt;code&gt;out.js&lt;&#x2F;code&gt; during the build. &lt;code&gt;out.js&lt;&#x2F;code&gt; is referred to as a
&quot;bundle&quot; (hence &lt;code&gt;--bundle&lt;&#x2F;code&gt;) because it now contains our application and all of
its dependencies.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;npm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; run build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After running the build, you can open up &lt;code&gt;index.html&lt;&#x2F;code&gt; in your browser and see
your new React app in all of its glory.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-should-new-developers-actually-use-when-starting-new-projects&quot;&gt;What should new developers actually use when starting new projects?&lt;&#x2F;h2&gt;
&lt;p&gt;Look, meta-frameworks like Next.js and Remix are rad. They bake in a ton of best
practices and make it easy to deliver very complicated web applications while
avoiding the pitfalls of a single page application. That said, I don&#x27;t think
they&#x27;re a good place for a beginner to start.&lt;&#x2F;p&gt;
&lt;p&gt;Instead, poke around with the different build tools that underly these
frameworks and learn how those megabytes of JS are actually delivered to your
end users. Focus on learning the principles of React alone, and not the
meta-framework glue that gives so many fancy features beneath layered
abstractions.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hypermedia and Hyperbole</title>
        <published>2023-10-26T00:00:00+00:00</published>
        <updated>2023-10-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-10-26-hyperbole/"/>
        <id>https://mgmarlow.com/words/2023-10-26-hyperbole/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-10-26-hyperbole/">&lt;p&gt;My partner and I are slowly working our way through the Myst series and have
finally started Myst 3: Exile. We were both surprised by how much of a departure
this entry is to the previous Myst games. The biggest change isn&#x27;t so much the
game itself as it is the implementation of freelook: the ability to look around
a scene like it&#x27;s a three-dimensional room. If this doesn&#x27;t sound like a big
achievement believe me, it is. The first two games are glorified powerpoint
slides in comparison.&lt;&#x2F;p&gt;
&lt;p&gt;Original Myst (not talking about the remakes) was effectively a deck of static
images with some clickable areas on top. Clicking on one of these areas could
advance you to the next scene (i.e. movement) or jiggle an object so as to
trigger an animation and play some sound effects. While the core gameplay and
presentation is incredibly simple, it doesn&#x27;t detract too much from the
compelling setting and (mostly) interesting puzzles.&lt;&#x2F;p&gt;
&lt;p&gt;I was fascinated to learn that the control mechanisms for Myst 1 and 2 root back
to early Cyan, before Myst was a thing. Rand and his brother Robyn made
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;63620&#x2F;Cosmic_Osmo_and_the_Worlds_Beyond_the_Mackerel&#x2F;&quot;&gt;games for the Apple Macintosh&lt;&#x2F;a&gt;
with a piece of software called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;HyperCard&quot;&gt;HyperCard&lt;&#x2F;a&gt;, an application for
building GUIs based on the principles of hypermedia.&lt;&#x2F;p&gt;
&lt;p&gt;All programs built in HyperCard consist of just two components: cards that store
data and links that navigate between those cards or execute scripts. It&#x27;s a
simple system that offers a lot of flexibility; even today I think it would make
an awesome prototyping tool. It also seems no coincidence that the scripting
language is named HyperTalk, as the whole environment reminds me of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Smalltalk&quot;&gt;Smalltalk&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;All of the concepts of HyperCard (and the awesome late-80s nerd aesthetic) are
well-explained in this interview with the creator, Bill Atkinson, and an
enthusiast, Danny Goodman:&lt;&#x2F;p&gt;
&lt;iframe
  width=&quot;560&quot;
  height=&quot;315&quot;
  src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;FquNpWdf9vg?si=nLlvTXjaWauEQCn2&quot;
  title=&quot;YouTube video player&quot;
  frameborder=&quot;0&quot;
  allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media;
  gyroscope; picture-in-picture; web-share&quot;
  allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;If that video piqued your interest,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;archive.org&#x2F;details&#x2F;The_Complete_HyperCard_Handbook&quot;&gt;&lt;em&gt;The Complete HyperCard Handbook&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;,
written by Danny Goodman in the video, is available to read online.&lt;&#x2F;p&gt;
&lt;p&gt;It always amazes me what creative people can create using simple tools.
HyperCard and the early days of Cyan are no exception; it seems like the duo
were able to find a lot of success by excelling within the constraints of their
software.&lt;&#x2F;p&gt;
&lt;p&gt;Hypermedia systems are super interesting to me, particularly with the renewed
interest in tools like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;htmx.org&#x2F;&quot;&gt;htmx&lt;&#x2F;a&gt; that promise to bring the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hypermedia.systems&#x2F;&quot;&gt;hypermedia ethos&lt;&#x2F;a&gt; back to the web. That said, this
post isn&#x27;t going to talk about how htmx is fighting against the SPA-explosion
that the web industry has experienced over the past decade. Instead, I&#x27;d like to
take a look at a far more esoteric hypermedia system:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;hyperbole&#x2F;&quot;&gt;GNU Hyperbole&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I stumbled on this Emacs package recently and I had this overwhelming sense of
deja vu due to its similarities to HyperCard. It was originally created in the
90s and retains an identity true to that era through some of its naming
conventions (e.g. HyRolo, the rolodex extension). But more than that it is a
full hypermedia engine, just like HyperCard, that is scriptable with Emacs Lisp.&lt;&#x2F;p&gt;
&lt;p&gt;The fact that Emacs contains an entire hypermedia system as an external package
is probably unsurprising given its reputation as an operating system, but do
people actually use it? The answer is yes, and rather extensively given the
amount of material about it on the internet. There were no less than three talks
about it during last year&#x27;s EmacsConf:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacsconf.org&#x2F;2022&#x2F;talks&#x2F;buttons&#x2F;&quot;&gt;Linking personal info with Hyperbole implicit buttons&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacsconf.org&#x2F;2022&#x2F;talks&#x2F;rolodex&#x2F;&quot;&gt;Build a Zettelkasten with the Hyperbole Rolodex&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacsconf.org&#x2F;2022&#x2F;talks&#x2F;hyperorg&#x2F;&quot;&gt;Powerful productivity with Hyperbole and Org Mode&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;All there are interesting glimpses into some of the functionality of Hyperbole,
but I found that Ramin Honary&#x27;s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacsconf.org&#x2F;2022&#x2F;talks&#x2F;rolodex&#x2F;&quot;&gt;Building a Zettelkasten&lt;&#x2F;a&gt; did the
best job of answering the question of &quot;why would anyone use this?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;As the title suggests, Ramin&#x27;s talk demonstrates a Zettelkasten built on
Hyperbole, using its built-in rolodex to store entries with scripted links
between them. That&#x27;s right, I said rolodex. Hyperbole is from the 90s, remember?&lt;&#x2F;p&gt;
&lt;p&gt;That said, a rolodex is probably not the best way to think about Hyperbole&#x27;s
record-keeping implementation (named HyRolo). It&#x27;s not so much about managing
contacts as it is managing record-oriented information. From that perspective it
feels like a natural fit for a Zettelkasten, which literally means &quot;slip box&quot; in
German.&lt;&#x2F;p&gt;
&lt;p&gt;Linking to a zettel in Hyperbole might look something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;hyrolo-fgrep &amp;quot;Immanuel Kant&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this case, the link reveals its implementation. It&#x27;s a call to an Emacs Lisp
function that greps over all HyRolo records for the string matching &quot;Immanuel
Kant&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, you don&#x27;t actually need to have an ugly link like that in your zettel
text. Instead, you can create what&#x27;s called an &quot;explicit button&quot; that hides the
actual linking function code in a separate file. With an explicit button your
zettel looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;(Immanuel Kant)&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This link behaves in an identical fashion to the previous one, even though it
hides away the details of the Emacs Lisp function powering the search. What&#x27;s
interesting is where it places those details. If you check your Hyperbole user
directory, you can see the source code of the new button in a special file:
&lt;code&gt;_hypb&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&amp;quot;immanuel_kant&amp;quot; nil nil hyrolo-fgrep (&amp;quot;Immanuel Kant&amp;quot; nil) &amp;quot;me@my-pc&amp;quot; &amp;quot;20231025:04:20:37&amp;quot; nil nil)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s just s-expressions all the way down!&lt;&#x2F;p&gt;
&lt;p&gt;Hyperbole is difficult to explain because it&#x27;s such an abstract concept. Sure we
can link between things, but what&#x27;s the use? I think looking at the &lt;code&gt;_hypb&lt;&#x2F;code&gt; file
and uncovering the Emacs Lisp engine underneath helps illustrate its
flexibility; it&#x27;s more than a linking engine because you can make your links any
valid Emacs Lisp expression. Pretty cool.&lt;&#x2F;p&gt;
&lt;p&gt;Now, I don&#x27;t think anyone is likely to create an entire game with Hyperbole,
like Cyan did with HyperCard. Not because they couldn&#x27;t per se, but asking
players to learn Emacs as a prerequisite feels a tad much. But if there&#x27;s
anything to learn from the success of HyperCard, it&#x27;s that simple interfaces
empower creative people to build interesting things and I think Hyperbole
exemplifies that same characteristic.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Recently</title>
        <published>2023-10-09T00:00:00+00:00</published>
        <updated>2023-10-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-10-08-recently/"/>
        <id>https://mgmarlow.com/words/2023-10-08-recently/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-10-08-recently/">&lt;blockquote&gt;
&lt;p&gt;I&#x27;m taking a page from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;macwright.org&quot;&gt;Tom MacWright&#x27;s blog&lt;&#x2F;a&gt; and am
trying out a &quot;Recently&quot; series, where I summarize a few things on my mind
since my last post.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Recently I&#x27;ve been tinkering with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SystemCrafters&#x2F;crafted-emacs&quot;&gt;Crafted Emacs&lt;&#x2F;a&gt;, a modularized
Emacs starter kit. The project strongly invokes one of my favorite things about
the Emacs community: taking bits of insight from other configurations and
sprinkling them into your own. The modules from the framework are meant to be
extended or replaced, so they exemplify good Emacs Lisp style and are filled
smart ideas and thorough documentation.&lt;&#x2F;p&gt;
&lt;p&gt;The project is undergoing a major overhaul with its
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SystemCrafters&#x2F;crafted-emacs&#x2F;tree&#x2F;craftedv2RC1&quot;&gt;craftedv2RC1 branch&lt;&#x2F;a&gt;
but it&#x27;s ready for general use (just don&#x27;t forget to clone the right branch).
I&#x27;ve been using it for the past few weeks in a professional capacity and have
landed on a pretty lightweight configuration
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.sr.ht&#x2F;~mgmarlow&#x2F;crafted-config&quot;&gt;my config&lt;&#x2F;a&gt;). It&#x27;s been a good
excuse to start experimenting with the new Emacs 29 Tree Sitter features, most
of which Crafted Emacs configures automatically.&lt;&#x2F;p&gt;
&lt;p&gt;Tree Sitter is actually surprising complicated to set up even with all of the
work that&#x27;s gone into Emacs 29. Grammars are shipped separately from the editor,
so you need to install each one from its respective git repository and compile
it before you can use it. Emacs provides a few commands to help this process but
there&#x27;s still lots of holes in the experience.&lt;&#x2F;p&gt;
&lt;p&gt;Thanks to Crafted Emacs I learned about
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;renzmann&#x2F;treesit-auto&quot;&gt;treesit-auto&lt;&#x2F;a&gt;, a library that strives
to make the Tree Sitter setup completely automatic. I imagine that as Tree
Sitter gains greater mindshare and major mode development lots of these issues
will be ironed out. Right now it feels a bit bleeding edge.&lt;&#x2F;p&gt;
&lt;p&gt;I discovered Crafted Emacs through the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;systemcrafters.net&#x2F;&quot;&gt;System Crafters&lt;&#x2F;a&gt; community, which by no
coincidence is also where I got started with Emacs a year or so ago. Lots of
friendly folks working on interesting things over there.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reading&quot;&gt;Reading&lt;&#x2F;h2&gt;
&lt;p&gt;I just finished up
&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;The_Wind-Up_Bird_Chronicle&quot;&gt;The Wind-up Bird Chronicle&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;
and it&#x27;s been lingering in my mind ever since. I really don&#x27;t know how to
summarize my feelings about Murakami; I&#x27;ve read four of his books so far
(novels, short story collections, and his memoir) and yet I can&#x27;t really say
whether I like his work. I suppose here I am reading and thinking about it, so
that must mean something.&lt;&#x2F;p&gt;
&lt;p&gt;Murakami&#x27;s work walks a line of allegory and emotional expression that is
surprising given the simplicity of the prose. The style of writing is just so
&lt;em&gt;plain&lt;&#x2F;em&gt; that it can&#x27;t help but be compelling in its emotional density. His
references to musical pieces, drinking beer, and reading books reminds me of my
dad.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;side-projects&quot;&gt;Side projects&lt;&#x2F;h2&gt;
&lt;p&gt;Crafted Emacs has been the bulk of my open source contribution lately but I&#x27;ve
also been messing around with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fennel-lang.org&#x2F;&quot;&gt;Fennel&lt;&#x2F;a&gt;, a Lisp that
compiles to Lua. The killer feature for me is programming games with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;love2d.org&#x2F;&quot;&gt;Love2D&lt;&#x2F;a&gt; and avoiding Lua altogether, since I don&#x27;t care
much for the language. Bonus points since it&#x27;s a Lisp and I&#x27;ve been on a Lisp
kick for the last two years.&lt;&#x2F;p&gt;
&lt;p&gt;I don&#x27;t have any big plans for the language other than to make a few games and
see what happens. Love2D is far and away my favorite game framework, having
worked with some alternatives like Raylib, HaxeFlixel, and Monogame. It&#x27;s
well-documented and robust, yet remains simple and expressive. It also has an
extensive amount of community material and libraries that provide just about
everything you&#x27;d want in a 2D engine. Fortunately Fennel retains the ability to
use these libraries since it just compiles down to Lua.&lt;&#x2F;p&gt;
&lt;p&gt;I discovered Fennel via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;andreyor.st&#x2F;&quot;&gt;Andrey Listopadov&#x27;s blog&lt;&#x2F;a&gt;, where
he documents some of his past games and some other really
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;andreyor.st&#x2F;posts&#x2F;2022-09-26-reproducible-research-with-org-mode-fennel-and-love&#x2F;&quot;&gt;insightful posts&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Deno + Tree Sitter + Emacs</title>
        <published>2023-08-31T00:00:00+00:00</published>
        <updated>2023-08-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-08-31-deno-tree-sitter-emacs/"/>
        <id>https://mgmarlow.com/words/2023-08-31-deno-tree-sitter-emacs/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-08-31-deno-tree-sitter-emacs/">&lt;p&gt;I&#x27;ve been spending a bunch of time fiddling with Deno lately, mostly in the form
of small scripts and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fresh.deno.dev&#x2F;&quot;&gt;Fresh projects&lt;&#x2F;a&gt;. One thing that
hasn&#x27;t impressed me doesn&#x27;t have anything to do with Deno itself but rather the
tooling around it: setting up Deno with Emacs is a little cumbersome. The
problem roots to Deno and TypeScript sharing a file extension (&lt;code&gt;.ts&lt;&#x2F;code&gt;). While
this doesn&#x27;t cause issue for syntax highlighting (since it&#x27;s the same for both
languages), it does cause issue when picking an appropriate language server.
Since Emacs generally uses file extensions to associate languages and
functionality it&#x27;s a bit awkward to have Emacs pick the right LSP program.&lt;&#x2F;p&gt;
&lt;p&gt;For individual projects you can use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Directory-Variables.html&quot;&gt;Directory Variables&lt;&#x2F;a&gt;
to override Eglot settings just for that directory, telling Eglot that it
belongs to a Deno project and not a regular TypeScript project. This approach
gets old fast, I don&#x27;t want to have to create a &lt;code&gt;dir-locals.el&lt;&#x2F;code&gt; file just to get
my Deno tooling working.&lt;&#x2F;p&gt;
&lt;p&gt;This lead me to hacking together some Deno project detection into my Emacs
configuration. Basically, if Emacs is in a project (that is, a
version-controlled folder) and finds a &lt;code&gt;deno.json&lt;&#x2F;code&gt; file, Eglot will select the
Deno language server instead of the TypeScript language server. In all other
cases, it defaults to TypeScript.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; deno-ts-project-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;when-let*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;project &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;project-current&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;              (&lt;&#x2F;span&gt;&lt;span&gt;p-root &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;project-root project&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;file-exists-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;concat&lt;&#x2F;span&gt;&lt;span&gt; p-root &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;deno.json&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; ts-server-program&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;&amp;amp;rest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; _&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cond&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;deno-project-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;deno&amp;quot; &amp;quot;lsp&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;initializationOptions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                                         (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt;enable t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;lint t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;         (&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;typescript-language-server&amp;quot; &amp;quot;--stdio&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;eglot-server-programs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;(&lt;&#x2F;span&gt;&lt;span&gt;web-mode &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; ts-server-program&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This technique works great, provided you always version control your Deno
projects. Emacs 29 introduces a new variable that we can use to improve project
detection for Deno, relying solely on the presence of a &lt;code&gt;deno.json&lt;&#x2F;code&gt; file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;project-vc-extra-root-markers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;deno.json&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These changes got me thinking, why not create a major mode for Deno? That way
all of these configuration options could be shipped with the major mode and
Emacs would inform the user in their mode-line whether they&#x27;re working in a
TypeScript project or a Deno project. Moreover, since we&#x27;re already using Emacs
29 features, what if that major mode was built on tree-sitter? It&#x27;s a great
excuse for finally trying that library out. And so I set out to build
&lt;code&gt;deno-ts-mode&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Setting up tree-sitter requires a few extra steps. Firstly, you need
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;download.html&quot;&gt;Emacs 29.1&lt;&#x2F;a&gt; (the most recent
release) installed. Secondly, you must compile
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tree-sitter.github.io&#x2F;tree-sitter&#x2F;using-parsers&quot;&gt;parsers&lt;&#x2F;a&gt; for each
language you want to use. For Deno, that means a TypeScript parser and a TSX
parser.&lt;&#x2F;p&gt;
&lt;p&gt;Assuming &lt;code&gt;cc&lt;&#x2F;code&gt; is available on your system, you can install both parsers with
this script:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq treesit-language-source-alist&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      &amp;#39;((&lt;&#x2F;span&gt;&lt;span&gt;typescript &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;github.com&#x2F;tree-sitter&#x2F;tree-sitter-typescript&amp;quot; &amp;quot;master&amp;quot; &amp;quot;typescript&#x2F;src&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span&gt;tsx &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;github.com&#x2F;tree-sitter&#x2F;tree-sitter-typescript&amp;quot; &amp;quot;master&amp;quot; &amp;quot;tsx&#x2F;src&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mapc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; #&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;treesit-install-language-grammar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;mapcar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; #&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;car treesit-language-source-alist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The parsers are installed to your Emacs user directory as DLLs. You can test
that everything installed correctly by trying out &lt;code&gt;typescript-ts-mode&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;With tree-sitter good to go, let&#x27;s create a new major mode for Deno that&#x27;s based
on the TypeScript tree-sitter mode:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;define-derived-mode&lt;&#x2F;span&gt;&lt;span&gt; deno-ts-mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  typescript-ts-mode &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Deno&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;Major mode for Deno.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;group&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;deno-ts-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At this stage, &lt;code&gt;deno-ts-mode&lt;&#x2F;code&gt; is near identical to &lt;code&gt;typescript-ts-mode&lt;&#x2F;code&gt;. The
only difference is that Deno shows up in your mode-line when &lt;code&gt;deno-ts-mode&lt;&#x2F;code&gt; is
activated, all syntax highlighting is inherited from &lt;code&gt;typescript-ts-mode&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The next step is to figure out whether a visited &lt;code&gt;.ts&lt;&#x2F;code&gt; file should use our new
Deno mode or the TypeScript mode. For this we can use our earlier function,
&lt;code&gt;deno-project-p&lt;&#x2F;code&gt;, and apply it to &lt;code&gt;auto-mode-alist&lt;&#x2F;code&gt;, the association list
mapping file extensions to major modes. Since &lt;code&gt;auto-mode-alist&lt;&#x2F;code&gt; accepts a
function, we can create a couple of wrappers around the two major modes in
question to resolve based on the result of &lt;code&gt;deno-project-p&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; deno-ts--ts-auto-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cond&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;deno-ts-project-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span&gt;deno-ts-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;typescript-ts-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; deno-ts--tsx-auto-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cond&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;deno-ts-project-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span&gt;deno-ts-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;tsx-ts-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;auto-mode-alist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;\\.ts\\&amp;#39;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span&gt; deno-ts--ts-auto-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;auto-mode-alist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;\\.tsx\\&amp;#39;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span&gt; deno-ts--tsx-auto-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When visiting a &lt;code&gt;.ts&lt;&#x2F;code&gt; or &lt;code&gt;.tsx&lt;&#x2F;code&gt; file, Emacs will smartly select either
&lt;code&gt;deno-ts-mode&lt;&#x2F;code&gt; or &lt;code&gt;typescript-ts-mode&lt;&#x2F;code&gt; (or TSX) based on the presence of a
&lt;code&gt;deno.json&lt;&#x2F;code&gt; file. Pretty great!&lt;&#x2F;p&gt;
&lt;p&gt;This simplifies our Eglot setup considerably, since there&#x27;s no need to check
&lt;code&gt;deno-project-p&lt;&#x2F;code&gt;. The major mode has solved that already!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; eglot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;deno-ts-mode &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; eglot-ensure&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;eglot-server-programs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               &amp;#39;(&lt;&#x2F;span&gt;&lt;span&gt;deno-ts-mode &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;deno&amp;quot; &amp;quot;lsp&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;initializationOptions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                                              (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt;enable t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;lint t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These features plus a few more (task automation, accepting &lt;code&gt;deno.json&lt;&#x2F;code&gt; and
&lt;code&gt;deno.jsonc&lt;&#x2F;code&gt;) are all available in my package
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.sr.ht&#x2F;~mgmarlow&#x2F;deno-ts-mode&quot;&gt;deno-ts-mode&lt;&#x2F;a&gt;. Give it a try and let
me know what you think!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building a Compiler for My Static Site Generator</title>
        <published>2023-07-26T00:00:00+00:00</published>
        <updated>2023-07-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-07-26-compiler-ssg/"/>
        <id>https://mgmarlow.com/words/2023-07-26-compiler-ssg/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-07-26-compiler-ssg/">&lt;p&gt;My side project has exploded in scope. My original goal was to build a static
site generator to learn how they work, not to support lots of features. Yet here
I am building a compiler for my own template language.&lt;&#x2F;p&gt;
&lt;p&gt;Before the compiler, I had a very simple model for handling templates focused
around variable replacement. That is, given some
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mustache.github.io&#x2F;&quot;&gt;mustache expression&lt;&#x2F;a&gt;, replace the requested
variable with one from the parent environment.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; An example from Orgify&amp;#39;s test suite&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;assert-compile-to-string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    &amp;quot;Hello, {{ name }}!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;expected&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Hello, world!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      &amp;#39;((&lt;&#x2F;span&gt;&lt;span&gt;name &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;world&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Originally I had implemented these substitutions via search and replace,
something Emacs is adept at. It looked something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; parse-handlebars&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;handlebars&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string-trim&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;substring&lt;&#x2F;span&gt;&lt;span&gt; handlebars &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; handlebars&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; search-and-replace-handlebars&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;re-search-forward&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;{{[ ]*[a-z]*[ ]*}}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;expr &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;save-match-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                       (&lt;&#x2F;span&gt;&lt;span&gt;parse-handlebars &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;unless&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;alist-get&lt;&#x2F;span&gt;&lt;span&gt; expr env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;concat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Unrecognized variable: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;replace-match&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;alist-get&lt;&#x2F;span&gt;&lt;span&gt; expr env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I was happy with this solution because it solved variable replacement in a super
simple, Emacs-y way. I soon learned, however, that this method is very difficult
to extend. It expects too many assumptions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The code operates within the context of a single buffer&lt;&#x2F;li&gt;
&lt;li&gt;Expressions are not multi-line&lt;&#x2F;li&gt;
&lt;li&gt;Expressions are always immediately replaced&lt;&#x2F;li&gt;
&lt;li&gt;The content of an expression is always a value from an alist (association
list)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;With this in mind, I ditched the prior code in favor of a more traditional,
compiler-driven approach. The new Orgify template language goes through the
usual tokenize, parse, and generate cycle. The result resembles
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;handlebarsjs.com&#x2F;&quot;&gt;Handlebars&lt;&#x2F;a&gt;, but with the added ability to execute
Emacs Lisp expressions.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; You can include any Emacs Lisp code in mustache templates:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;assert-compile-to-string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;{{ (+ 1 41) }}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;expected&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;42&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The rest of this post describes how the new compiler works.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-the-compiler&quot;&gt;Building the compiler&lt;&#x2F;h2&gt;
&lt;p&gt;Orgify&#x27;s compiler has three phases: tokenization, parsing, and code generation
(although the last two steps are actually performed at the same time).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tokenization&quot;&gt;Tokenization&lt;&#x2F;h3&gt;
&lt;p&gt;The goal of tokenization is to make the text easier to parse by breaking it down
into smaller tokens that pick out language symbols. This step is surprisingly
crucial. The difference in a program&#x27;s ability to understand a list of tokens
vs. raw text is night and day.&lt;&#x2F;p&gt;
&lt;p&gt;My template language only cares about a few tokens:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;obrace&lt;&#x2F;code&gt;: opening handlebars expression&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cbrace&lt;&#x2F;code&gt;: closing handlebars expression&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;oeach&lt;&#x2F;code&gt;: opening loop expression&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;ceach&lt;&#x2F;code&gt;: closing loop expression&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;text&lt;&#x2F;code&gt;: everything else, e.g. HTML or Emacs Lisp&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The scope of each token is intentionally small. It&#x27;s very easy to accidentally
blur the line between the job of the tokenizer and the job of the parser by
trying to capture tokens that contain too much information. This path leads only
to headaches.&lt;&#x2F;p&gt;
&lt;p&gt;Finding these tokens in the original text still relies on regular expressions,
though the implementation is quite a bit different from the original approach.
Rather than using &lt;code&gt;re-search-forward&lt;&#x2F;code&gt;, which operates on a buffer, I use
&lt;code&gt;string-match&lt;&#x2F;code&gt;, which operates on a string. Additionally, I use an incrementing
index to ensure the regular expression is always matching against the beginning
of the current position in the string (important due to some quirks in Emacs
Lisp regular expression language).&lt;&#x2F;p&gt;
&lt;p&gt;Altogether, tokenization looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; tokenize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;tokens &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;()) (&lt;&#x2F;span&gt;&lt;span&gt;cur-text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) (&lt;&#x2F;span&gt;&lt;span&gt;idx &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    ;; A helper function to append text tokens&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cl-flet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;purge-text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; cur-text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;text cur-text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                  (&lt;&#x2F;span&gt;&lt;span&gt;setq cur-text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      ;; Looping over the input string, one character at a time&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; idx &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;        ;; Need to use (eq ... idx) to ensure regex is matching from&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;        ;; idx onwards (e.g. start of string only).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cond&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string-match&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;{{&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; input idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span&gt;purge-text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; `(&lt;&#x2F;span&gt;&lt;span&gt;obrace ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt; input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span&gt;setq idx &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;1-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;              ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string-match&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;}}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; input idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span&gt;purge-text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; `(&lt;&#x2F;span&gt;&lt;span&gt;cbrace ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt; input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span&gt;setq idx &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;1-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;              ;; ...snip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;              ;; For everything else, just append the character&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;              ;; to cur-text.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;              (&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span&gt;setq cur-text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;concat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                cur-text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                                (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;char-to-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;aref&lt;&#x2F;span&gt;&lt;span&gt; input idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cl-incf&lt;&#x2F;span&gt;&lt;span&gt; idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;purge-text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;reverse&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For every index in the string, decide whether the string starting at that index
matches one of the language tokens via regular expression. If it does, purge any
text that might be hanging around from previous iterations and push a new token
to the list. If it doesn&#x27;t, append the current character to the growing string
of characters for the next text purge.&lt;&#x2F;p&gt;
&lt;p&gt;It might be easier to visualize by looking at an example template:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Template:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;lt;p&amp;gt;Hello {{ name }}!&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;   &amp;lt;ul&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;     #each page in pages&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;       &amp;lt;li&amp;gt;{{ page }}&amp;lt;&#x2F;li&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;;     &#x2F;each&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;lt;&#x2F;ul&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Tokens:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;((&lt;&#x2F;span&gt;&lt;span&gt;text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&amp;lt;p&amp;gt;Hello &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;obrace &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;{{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;cbrace &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;}}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;!&amp;lt;&#x2F;p&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;lt;ul&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n  &amp;lt;li&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;oeach &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;#each page in pages&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;obrace &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;{{&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;page&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;cbrace &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;}}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n  &amp;lt;&#x2F;li&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;ceach &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&#x2F;each&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;text &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;lt;&#x2F;ul&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;parsing-and-code-generation&quot;&gt;Parsing and code generation&lt;&#x2F;h3&gt;
&lt;p&gt;The next step in compilation feeds these tokens to the parser. The parser runs
through the list of tokens and gives them structure, appending them as leaves
and branches to an abstract syntax tree (AST).&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;expressions &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;()) (&lt;&#x2F;span&gt;&lt;span&gt;cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;token &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;nth&lt;&#x2F;span&gt;&lt;span&gt; cur tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cond&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span&gt; token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;               ;; Handle text...)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;              ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;obrace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span&gt; token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;               ;; Handle opening braces...)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;              ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;cbrace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span&gt; token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;               ;; Handle closing braces...)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;setq cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;1+&lt;&#x2F;span&gt;&lt;span&gt; cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;reverse&lt;&#x2F;span&gt;&lt;span&gt; expressions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The call to &lt;code&gt;reverse&lt;&#x2F;code&gt; here might be unexpected, but it&#x27;s a common Lisp-ism since
&lt;code&gt;push&lt;&#x2F;code&gt; prepends items to the front of the list. Reversing &lt;code&gt;expressions&lt;&#x2F;code&gt; ensures
the order of the tree matches the order of the original tokens.&lt;&#x2F;p&gt;
&lt;p&gt;I commented out the implementation of each token branch because there are some
important prerequisite topics to cover: quoting and eval. These two tools are
crucial for this compiler, so it&#x27;s worth explaining them with some extra detail.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;quoting&quot;&gt;Quoting&lt;&#x2F;h4&gt;
&lt;p&gt;There&#x27;s a very interesting consequence of building an AST for this compiler in
Lisp. Because Lisp code is naturally structured as lists, the parser able to
directly generate a tree of Emacs Lisp code, rather than a tree of generic nodes
that need another layer of translation. This is accomplished through quoting, a
Lisp special form that returns an object without evaluating it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; =&amp;gt; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; =&amp;gt; (+ 1 2)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The ability to pend evaluation by quoting is incredibly useful for a compiler
that generates instructions. Every node in the AST generated by the parser is a
quoted Emacs expression. It satisfies not only the AST data structure, a tree
representing the program shape, but also the code that need be generated from
the source tokens.&lt;&#x2F;p&gt;
&lt;p&gt;This aspect of code-as-data in Lisp is referred to as
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Homoiconicity&quot;&gt;homoiconicity&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Taking it one step further, the backtick character enables mixing quoting and
evaluation. This is how macros are generally written in Lisp.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq value &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;42&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;`(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span&gt; value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; =&amp;gt; (+ 5 value)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;`(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ,&lt;&#x2F;span&gt;&lt;span&gt;value&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; =&amp;gt; (+ 5 42)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Anywhere a comma falls is an expression that is evaluated. By mixing quoted
forms and evaluation, it&#x27;s easy to construct complex snippets of code for the
compiler.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;evaluating-emacs-lisp-expressions&quot;&gt;Evaluating Emacs Lisp expressions&lt;&#x2F;h4&gt;
&lt;p&gt;Given that the parser generates quoted Emacs Lisp code, how does the compiler
actually evaluate it? With &lt;code&gt;eval&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;eval&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; =&amp;gt; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;One problem remains, however. My original approach relied on &lt;code&gt;alist-get&lt;&#x2F;code&gt; to
insert values from the parent environment into the source template when
replacing regular expressions. This assumed that all text inside mustache braces
was represented by a key-value pair in an association list.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Recall, replacing a variable in a template&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;assert-compile-to-string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;    &amp;quot;Hello, {{ name }}!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;expected&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Hello, world!&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      &amp;#39;((&lt;&#x2F;span&gt;&lt;span&gt;name &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;world&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;How does &lt;code&gt;eval&lt;&#x2F;code&gt; similarly replace variables from the parent environment? The key
is the third argument of &lt;code&gt;eval&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Signature&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(eval FORM &amp;amp;optional LEXICAL)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Documentation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Evaluate FORM and return its value.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;If LEXICAL is t, evaluate using lexical scoping.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LEXICAL can also be an actual lexical environment, in the form of an&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;alist mapping symbols to their value.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If an alist is passed into &lt;code&gt;eval&lt;&#x2F;code&gt; it uses it as the lexical environment with
which variables are evaluated. This approach solves the problem of variable
substitution without hard-coding the use of &lt;code&gt;alist-get&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;eval&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;((&lt;&#x2F;span&gt;&lt;span&gt;name &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;world&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;quot;world&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When quoted symbols are evaluated, Emacs Lisp knows to look up that symbol from
the &lt;code&gt;ENV&lt;&#x2F;code&gt; alist for its value. Cool.&lt;&#x2F;p&gt;
&lt;p&gt;Requiring a quoted symbol for the variable actually poses a bit of a problem for
this compiler. When a layout is tokenized, that layout is input as a string. All
of the generated tokens reference string values. Since &lt;code&gt;eval&lt;&#x2F;code&gt; requires a symbol,
those strings need to be converted.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily this problem is easily solvable with the function &lt;code&gt;read-from-string&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;read-from-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;#39;(name . 4)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;eval&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;read-from-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)) &amp;#39;((&lt;&#x2F;span&gt;&lt;span&gt;name &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;world&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;quot;world&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h4 id=&quot;parsing&quot;&gt;Parsing&lt;&#x2F;h4&gt;
&lt;p&gt;With quoting and eval out of the way, it&#x27;s time to fill in the parser. For each
conditional branch against a token, the parser pushes a tree of Emacs Lisp
expressions into the AST. When mustaches are detected (e.g. &lt;code&gt;obrace&lt;&#x2F;code&gt;) a call to
&lt;code&gt;eval-string&lt;&#x2F;code&gt; is to pend the execution of that text as an Emacs Lisp expression.
Regular text is inserted as-is.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; lastcar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;l&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;Extract the last element from list L.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cdr&lt;&#x2F;span&gt;&lt;span&gt; l&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; eval-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;string env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;eval&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;read-from-string&lt;&#x2F;span&gt;&lt;span&gt; string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;span&gt; env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;expressions &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;()) (&lt;&#x2F;span&gt;&lt;span&gt;cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span&gt;token &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;nth&lt;&#x2F;span&gt;&lt;span&gt; cur tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;cond&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span&gt; token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; `(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span&gt; ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;lastcar token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;span&gt; expressions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;              ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;obrace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span&gt; token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;               ;; Assume that the only token between an obrace&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;               ;; and a cbrace is text.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; `(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;eval-string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                               ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;lastcar &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;nth&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;1+&lt;&#x2F;span&gt;&lt;span&gt; cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                               env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                     expressions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span&gt;setq cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt; cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;              ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;eq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;cbrace&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span&gt; token&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;               (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Unexpected closing brace&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;setq cur &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;1+&lt;&#x2F;span&gt;&lt;span&gt; cur&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      ;; ,@ means spill the contents, kind of like&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      ;; the ... operator in JS or Rust.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    `(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;lambda&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) ,@(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;reverse&lt;&#x2F;span&gt;&lt;span&gt; expressions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; How the generated code is actually executed.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; compile-and-exec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;input env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;funcall&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;parse &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;tokenize input&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;span&gt; env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s probably a little easier to look at the generated code:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Hello {{ name }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;lambda&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;Hello, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;eval-string &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;name&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The root of the tree is a lambda expression, taking the env as a single
argument. The lexical environment containing the page variables and other
metadata are assembled earlier in the static site generator and passed down as
an alist.&lt;&#x2F;p&gt;
&lt;p&gt;Everything else boils down to insert statements, writing a string into the
current buffer. What&#x27;s great about the generated code is the deferred evaluation
of &lt;code&gt;env&lt;&#x2F;code&gt;. The parser builds quoted forms to avoid working with &lt;code&gt;env&lt;&#x2F;code&gt; until the
very last minute, that is, when the lambda expression is evaluated. This keeps
the parser decoupled from anything that may happen in the lexical environment.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;compile-and-exec&lt;&#x2F;code&gt; is meant for use with a fresh buffer, since the insert
statements will mutate that buffer with their string arguments. Something like
&lt;code&gt;with-temp-file&lt;&#x2F;code&gt;, which will write the buffer contents to a new file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;with-temp-file&lt;&#x2F;span&gt;&lt;span&gt; destination-file&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;compile-and-exec template-string env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That about wraps it up. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgify.pages.dev&quot;&gt;Orgify&lt;&#x2F;a&gt; supports some
additional syntax not mentioned in this article, but hopefully it&#x27;s clear how
the components from the parser can be altered to add loops and conditionals.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Small Weekend Projects</title>
        <published>2023-07-05T00:00:00+00:00</published>
        <updated>2023-07-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-07-05-small-weekend-projects/"/>
        <id>https://mgmarlow.com/words/2023-07-05-small-weekend-projects/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-07-05-small-weekend-projects/">&lt;p&gt;I love small weekend projects. A project completed in two days is either smartly
scoped or hacked together; the core of a great idea that&#x27;s been sitting in the
back of your mind or a feat of engineering spontaneity that is given just enough
space to flourish into reality. Either way it&#x27;s a great way to learn new things.&lt;&#x2F;p&gt;
&lt;p&gt;I was compelled this weekend to build my own
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgify.pages.dev&quot;&gt;static site generator&lt;&#x2F;a&gt;, not because I found existing
ones lacking, but because I wanted one to call my own. The basics are easy to
get right but the details expand into a surprisingly interesting problem space.
I guess that&#x27;s one reason for why so many already exist.&lt;&#x2F;p&gt;
&lt;p&gt;I started by settling on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgmode.org&quot;&gt;Org&lt;&#x2F;a&gt; as my generator&#x27;s primary
markup format. It&#x27;s supported OOTB in Emacs and already supports HTML exports
(via &lt;code&gt;ox-html&lt;&#x2F;code&gt;) so I didn&#x27;t need to spend my weekend writing an exporter.
Instead, I focused on the basics. Copy some static files, convert the contents
of Org files to HTML, and substitute that content into HTML layouts.&lt;&#x2F;p&gt;
&lt;p&gt;Where things start getting interesting is with layouts and templates. There&#x27;s a
whole host of template expressions that you might want to support in a
generator, from basic substitutions to loops and conditionals. I learned that
Emacs Lisp has an incredibly elegant solution for handlebars-like template
substitutions:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;re-search-forward&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;{{[ ]*[a-z]*[ ]*}}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;gethash&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; template-content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;replace-match&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;gethash&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; template-content&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;expression not recognized: %s&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This code searches the current buffer for a regular expression matching
handlebars, like &lt;code&gt;{{ foobar }}&lt;&#x2F;code&gt;. As long as a match is found, lookup the match
in a hash-table containing the original file&#x27;s frontmatter (&lt;code&gt;template-content&lt;&#x2F;code&gt;).
Then, swap out the template expression in-place with its matching value via
&lt;code&gt;replace-match&lt;&#x2F;code&gt;. Super easy!&lt;&#x2F;p&gt;
&lt;p&gt;The final product looks something like this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Loop over the directory of Org files.&lt;&#x2F;li&gt;
&lt;li&gt;Use &lt;code&gt;ox-html&lt;&#x2F;code&gt; to parse an Org file, storing the HTML content and frontmatter
keywords separately.&lt;&#x2F;li&gt;
&lt;li&gt;Insert the Org file&#x27;s associated layout into a new buffer.&lt;&#x2F;li&gt;
&lt;li&gt;Search for handlebar expressions in the current buffer and substitute them
with the HTML from step 2.&lt;&#x2F;li&gt;
&lt;li&gt;Write the current buffer to a new HTML file in the user&#x27;s output directory.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;m super happy with where the project ended up as an MVP, though there&#x27;s still
a few features I need to implement before it can actually be used for a blog
(namely: template loops and collections).&lt;&#x2F;p&gt;
&lt;p&gt;You can check out the documentation (built in Orgify!) here:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgify.pages.dev&quot;&gt;https:&#x2F;&#x2F;orgify.pages.dev&lt;&#x2F;a&gt;. Or, read the source code:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.sr.ht&#x2F;~mgmarlow&#x2F;orgify&quot;&gt;https:&#x2F;&#x2F;git.sr.ht&#x2F;~mgmarlow&#x2F;orgify&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building a Flymake Backend for Clippy</title>
        <published>2023-06-19T00:00:00+00:00</published>
        <updated>2023-06-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-06-17-flymake-clippy/"/>
        <id>https://mgmarlow.com/words/2023-06-17-flymake-clippy/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-06-17-flymake-clippy/">&lt;p&gt;Last weekend I had a great time building my own Flymake backend for Clippy (the
Rust linter): &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;flymake-clippy&#x2F;&quot;&gt;&lt;code&gt;flymake-clippy&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.
If you haven&#x27;t heard of Flymake, it&#x27;s old-school Emacs tech for showing
squiggly-lines in your editor. You can build your own Flymake extensions by
creating a function, referred to as a backend, that collects diagnostics and
reports them to Flymake. Register that backend in your Emacs config and you&#x27;ve
got squiggles whenever you open up a Rust file. Pretty cool!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;flymake-clippy&lt;&#x2F;code&gt; is an extension of the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_mono&#x2F;flymake.html#An-annotated-example-backend&quot;&gt;annotated example&lt;&#x2F;a&gt;
found in the Emacs Manual. While the example serves as a great starting point, I
found that in practice it leaves too many details unexplained. Most of my
development time was spent looking through the Emacs documentation, learning
about regular expression match groups, external processes, and buffer searching.
There were also some surprises when trying to integrate &lt;code&gt;flymake-clippy&lt;&#x2F;code&gt; with
Eglot, the LSP package that now ships with Emacs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-does-it-work&quot;&gt;How does it work?&lt;&#x2F;h2&gt;
&lt;p&gt;With Flymake mode active, each backend function included in the variable
&lt;code&gt;flymake-diagnostic-functions&lt;&#x2F;code&gt; is called on the current Emacs buffer. When the
&lt;code&gt;flymake-clippy&lt;&#x2F;code&gt; backend is invoked, the following things happen in sequence:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cargo clippy&lt;&#x2F;code&gt; is called and its output is thrown into a temporary buffer
(&lt;code&gt;*flymake-clippy*&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;A
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;elisp&#x2F;Sentinels.html&quot;&gt;process sentinel&lt;&#x2F;a&gt;
is triggered, invoking a callback that parses the contents of that temporary
buffer and collects diagnostic information into a list&lt;&#x2F;li&gt;
&lt;li&gt;That list of diagnostics is sent to Flymake via another callback function&lt;&#x2F;li&gt;
&lt;li&gt;The temporary buffer and process are cleaned up&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;These steps are repeated many times throughout the lifecycle of a single buffer,
creating and destroying squiggles as problems are introduced or addressed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;code-walkthrough&quot;&gt;Code walkthrough&lt;&#x2F;h2&gt;
&lt;p&gt;In a bit more detail, the first step is to create a process:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;make-process&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;flymake-clippy&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;noquery t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;connection-type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;pipe&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;generate-new-buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;*flymake-clippy*&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;command&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;cargo&amp;quot; &amp;quot;clippy&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; :&lt;&#x2F;span&gt;&lt;span&gt;sentinel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;lambda&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;proc _event&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;memq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;process-status&lt;&#x2F;span&gt;&lt;span&gt; proc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;exit&lt;&#x2F;span&gt;&lt;span&gt; signal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     ...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This process runs the command &lt;code&gt;cargo clippy&lt;&#x2F;code&gt;, our linter, and pipes its output
into the buffer &lt;code&gt;*flymake-clippy*&lt;&#x2F;code&gt; (named via the call to
&lt;code&gt;generate-new-buffer&lt;&#x2F;code&gt;). While that process is active, several events are sent to
the sentinel, invoking its respective lambda. &lt;code&gt;flymake-clippy&lt;&#x2F;code&gt; only cares about
the &lt;code&gt;exit&lt;&#x2F;code&gt; or &lt;code&gt;signal&lt;&#x2F;code&gt; events, which are checked via &lt;code&gt;process-status&lt;&#x2F;code&gt;. All other
events are ignored.&lt;&#x2F;p&gt;
&lt;p&gt;The core of &lt;code&gt;flymake-clippy&lt;&#x2F;code&gt; lives in the rest of that lambda function:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;lambda&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;proc _event&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  ;; ... snip ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;with-current-buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;process-buffer&lt;&#x2F;span&gt;&lt;span&gt; proc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;goto-char&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;point-min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cl-loop&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;search-forward-regexp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;            (&lt;&#x2F;span&gt;&lt;span&gt;flymake-clippy--build-regexp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;            nil&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     for&lt;&#x2F;span&gt;&lt;span&gt; msg &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     for&lt;&#x2F;span&gt;&lt;span&gt; sourcefile &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;beg &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; end&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;flymake-diag-region&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;                        source&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;                        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string-to-number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     for&lt;&#x2F;span&gt;&lt;span&gt; type &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string-match&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;^warning&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; msg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;                     :&lt;&#x2F;span&gt;&lt;span&gt;warning&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;                   :&lt;&#x2F;span&gt;&lt;span&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt; sourcefile &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;string-match-p&lt;&#x2F;span&gt;&lt;span&gt; sourcefile filename&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     collect&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;flymake-make-diagnostic source&lt;&#x2F;span&gt;&lt;span&gt; beg end type msg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     into&lt;&#x2F;span&gt;&lt;span&gt; diags&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;     finally&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;funcall&lt;&#x2F;span&gt;&lt;span&gt; report-fn diags&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;kill-buffer&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;process-buffer&lt;&#x2F;span&gt;&lt;span&gt; proc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This code is pretty dense but most of it is facilitating a loop through the
&lt;code&gt;*flymake-clippy*&lt;&#x2F;code&gt; buffer and parsing the Clippy output into variables.
&lt;code&gt;cl-loop&lt;&#x2F;code&gt; is a powerhouse looping macro that actually comes from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gigamonkeys.com&#x2F;book&#x2F;loop-for-black-belts.html&quot;&gt;Common Lisp&lt;&#x2F;a&gt;. It&#x27;s
accessible in Emacs through the library &lt;code&gt;cl-lib&lt;&#x2F;code&gt;, a compatibility library that
brings a bunch of Common Lisp functions&#x2F;macros into Emacs Lisp.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;while&lt;&#x2F;code&gt; keyword has this code searching via a regular expression, looking
for matches that are transformed into Flymake diagnostic output.&lt;&#x2F;p&gt;
&lt;p&gt;The regular expression for &lt;code&gt;flymake-clippy&lt;&#x2F;code&gt; looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;^\\(warning:.*\\)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n.*--&amp;gt; \\(.*\\):\\([0-9]+\\):\\([0-9]+\\)$&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Matching text like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;warning: using `clone` on type `Status` which implements the `Copy` trait&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --&amp;gt; src&#x2F;foo.rs:31:29&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At the core of the regexp are four match groups:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The message, prefixed with &quot;warning:&quot;&lt;&#x2F;li&gt;
&lt;li&gt;The file, prefixed with &quot;--&amp;gt;&quot;&lt;&#x2F;li&gt;
&lt;li&gt;The line number&lt;&#x2F;li&gt;
&lt;li&gt;The column number&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Each one of these match groups is assigned to a variable in &lt;code&gt;cl-loop&lt;&#x2F;code&gt; via the
&lt;code&gt;match-string&lt;&#x2F;code&gt; function, grabbing match data from the most recent regular
expression. Most of these variables are handed off to Flymake as-is, with the
exception of line number. Clippy&#x27;s line number needs a little translation (via
&lt;code&gt;flymake-diag-region&lt;&#x2F;code&gt;) so it&#x27;s useful in a buffer context.&lt;&#x2F;p&gt;
&lt;p&gt;Since Clippy runs against the entire Cargo project and not just a single file, I
also include a &lt;code&gt;when&lt;&#x2F;code&gt; expression to compare the open buffer filename against the
filename match group. Otherwise, messages from other files will show up in the
diagnostics.&lt;&#x2F;p&gt;
&lt;p&gt;With the &lt;code&gt;collect&lt;&#x2F;code&gt; keyword, &lt;code&gt;cl-loop&lt;&#x2F;code&gt; collects the diagnostic variables into a
list of Flymake data structures. These data structures are handed off to Flymake
via the callback: &lt;code&gt;(funcall report-fn diags)&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;testing-regular-expressions&quot;&gt;Testing regular expressions&lt;&#x2F;h2&gt;
&lt;p&gt;I found it much easier to iterate on a regular expression by writing tests
rather than manually executing Emacs commands, so I used
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_mono&#x2F;ert.html&quot;&gt;ERT&lt;&#x2F;a&gt; to run the
regular expression against a temporary buffer of Clippy output:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;flymake-clippy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;ert&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; run-regexp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  ;; Reset regexp match data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;set-match-data&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;search-forward-regexp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;flymake-clippy--build-regexp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;match-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;ert-deftest&lt;&#x2F;span&gt;&lt;span&gt; clippy-test-regexp &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;Tests regexp matches diagnostic information.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;should&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;   (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;equal&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    ;; Open a temp buffer with the contents of a test&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    ;; fixture that contains Clippy output&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;with-temp-buffer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;insert-file-contents&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;.&#x2F;test&#x2F;fixture.txt&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;run-regexp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;warning: unused variable: `user`&amp;quot; &amp;quot;src&#x2F;database&#x2F;foo.rs&amp;quot; &amp;quot;42&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This file is also a good demonstration of the regular expression match groups
that are passed into &lt;code&gt;flymake-make-diagnostic&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;working-with-eglot&quot;&gt;Working with Eglot&lt;&#x2F;h2&gt;
&lt;p&gt;I ran into a few surprises setting up &lt;code&gt;flymake-clippy&lt;&#x2F;code&gt; and Eglot in my
configuration. It turns out that Eglot hijacks Flymake, suppressing all other
Flymake backends while Eglot is running. This isn&#x27;t an issue with Eglot per se,
but a design decision (see
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;joaotavora&#x2F;eglot&#x2F;issues&#x2F;268&quot;&gt;eglot#268&lt;&#x2F;a&gt;); Eglot uses Flymake
to demonstrate LSP diagnostics and suppresses other backends to avoid duplicate
messages.&lt;&#x2F;p&gt;
&lt;p&gt;There is a workaround that I use in my configuration that allows Clippy and
Eglot to coexist:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; flymake-clippy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;vc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt;fetcher sourcehut &lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt;repo mgmarlow&#x2F;flymake-clippy&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rust-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span&gt; flymake-clippy-setup-backend&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; manually-activate-flymake&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;flymake-diagnostic-functions&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; #&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;eglot-flymake-backend&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; nil&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;flymake-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; eglot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;rust-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span&gt; eglot-ensure&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;         (&lt;&#x2F;span&gt;&lt;span&gt;eglot--managed-mode &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; manually-activate-flymake&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;eglot-stay-out-of&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;flymake&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;eglot-stay-out-of&lt;&#x2F;code&gt; disables Eglot&#x27;s control over Flymake. This opens up the
possibility of running other Flymake backends in a buffer with Eglot, but also
removes Eglot&#x27;s backend from the &lt;code&gt;flymake-diagnostic-functions&lt;&#x2F;code&gt; list. It&#x27;s
important to add &lt;code&gt;eglot-flymake-backend&lt;&#x2F;code&gt; back to that list, as well as manually
activate it during &lt;code&gt;eglot--managed-mode&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;flymake-vs-flycheck&quot;&gt;Flymake vs. Flycheck&lt;&#x2F;h2&gt;
&lt;p&gt;Those in the know may be wondering why I built a Flymake backend instead of one
for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.flycheck.org&#x2F;en&#x2F;latest&#x2F;index.html&quot;&gt;Flycheck&lt;&#x2F;a&gt;. The two libraries
are now very similar, though in past Emacs releases they deviated substantially.&lt;&#x2F;p&gt;
&lt;p&gt;Flymake is the older of the two (since Emacs 22) and is included in Emacs as a
built-in package. There was a period of time between Emacs 22 and 27 where
Flymake was not receiving much love and attention, languishing with some
long-standing issues. Flycheck rose during this time as a drop-in replacement.&lt;&#x2F;p&gt;
&lt;p&gt;Nowadays, thanks to the efforts of Eglot author João Távora, Flymake and
Flycheck are on mostly equal footing. I chose Flymake simply because it&#x27;s built
into Emacs and I didn&#x27;t want to introduce a new dependency. That said,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.flycheck.org&#x2F;en&#x2F;latest&#x2F;user&#x2F;flycheck-versus-flymake.html&quot;&gt;Flycheck vs. Flymake&lt;&#x2F;a&gt;
discusses the pros and cons in more detail.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;flymake-clippy&quot;&gt;flymake-clippy&lt;&#x2F;h2&gt;
&lt;p&gt;If you want to see Clippy warnings in your Rust buffers, check out
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;flymake-clippy&#x2F;&quot;&gt;flymake-clippy&lt;&#x2F;a&gt;. Refer to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;flymake-clippy&#x2F;tree&#x2F;main&#x2F;item&#x2F;README.md&quot;&gt;README&lt;&#x2F;a&gt;
for setup instructions.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Thoughts on Taiji</title>
        <published>2023-05-29T00:00:00+00:00</published>
        <updated>2023-05-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-05-03-taiji-first-impressions/"/>
        <id>https://mgmarlow.com/words/2023-05-03-taiji-first-impressions/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-05-03-taiji-first-impressions/">&lt;p&gt;As a big fan of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;210970&#x2F;The_Witness&#x2F;&quot;&gt;The Witness&lt;&#x2F;a&gt; I&#x27;m always
on the lookout for games that scratch a similar itch. Unfortunately that list is
fairly short; common recommendations include games of direct inspiration (Cyan&#x27;s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Myst&quot;&gt;Myst&lt;&#x2F;a&gt;), games with puzzle design expertise
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.stephenssausageroll.com&#x2F;&quot;&gt;Stephen&#x27;s Sausage Roll&lt;&#x2F;a&gt;), games with
meta-philosophical commentary
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;The_Talos_Principle&quot;&gt;The Talos Principle&lt;&#x2F;a&gt;),
among others. Although these games are great in their own way (and often equally
abstract or mysterious) they ultimately don&#x27;t play much at all like The Witness.&lt;&#x2F;p&gt;
&lt;p&gt;The element that&#x27;s lacking in these other games is the special sauce that makes
the puzzles in The Witness so satisfying: the gradual reveal of a complex puzzle
language that&#x27;s explained only by the player&#x27;s own tinkering and theory
crafting. There&#x27;s no direct tutorialization in The Witness, at least not in the
form of expository text. The tutorial &lt;em&gt;is the game itself&lt;&#x2F;em&gt;, every puzzle a
Rosetta Stone for some mechanic or interaction that is remixed, combined, or
built upon in future puzzles.&lt;&#x2F;p&gt;
&lt;p&gt;While this quality is present in the games I mentioned previously (as it&#x27;s a
necessary quality for a great puzzle game), it differs in The Witness because
the entire game is built around it. The Witness understands its method of
teaching and weaponizes it in its puzzle design, leading to puzzles layered in
mechanics that understand their history. It&#x27;s a language filled with idioms and
slang whose purpose is to bring epiphany.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;taiji-game.com&#x2F;&quot;&gt;Taiji&lt;&#x2F;a&gt; is about as close to The Witness as a game can
get without actually being The Witness. This similarity is both good and bad. On
one hand, Taiji evokes the same sense of wonderful epiphany that comes from
discovering its secrets. On the other, it is impossible to discuss without
comparing it to a game that is broader both in scope and team size.&lt;&#x2F;p&gt;
&lt;p&gt;This leads me to mixed feelings about Taiji. Although it clearly understands
what makes The Witness great and manages to evoke a similar experience while
establishing its own language, I found many of its puzzles messy and obscured by
its presentation.&lt;&#x2F;p&gt;
&lt;p&gt;This problem is mostly apparent in areas with environmental queues, like The
Ruins or The Graveyard. Many times I solved a puzzle thinking I understood the
designer&#x27;s intent, feeling good about my progress. Then I&#x27;d hit a wall on a
later puzzle where some concept I had missed re-emerges. Finding the solution
seems so far outside my assumptions that I lack a language for experimentation.
Instead, I frustrate myself by floundering around with oddball ideas. It&#x27;s only
after replaying earlier puzzles in the sequence that I recognize I had wrongly
interpreted some environmental cue or missed a subtle detail that drastically
changes the intended progression. I&#x27;m deprived of an &quot;Aha!&quot; moment, feeling more
like I was blinded by extraneous details than properly working through a
difficult problem.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s not to say all of the puzzles in the game have this issue, it just
happened to be most present in some of the first areas I ventured into. I found
the game at its best when the puzzles were constrained to the interactive grid,
no environmental details needed. I most enjoyed solving The Mine, The Shrine,
and The Mill for this reason. The areas of The Ruins, The Graveyard, and The
Gallery (easily my least favorite area) feel weaker due to the game&#x27;s
perspective and composition of environmental details. My issues with instruction
are less apparent when all of the details are constrained to a well-purposed
grid.&lt;&#x2F;p&gt;
&lt;p&gt;In a similar vein, my favorite puzzles were the ones in the canonical bad ending
of the game, The Black. Without spoiling too much, these puzzles flip the
interactive elements of the grid and completely change the way in which the
player approaches a solution. The language is the same but the tools are
inverted, the way you solve it re-applies everything you&#x27;ve learned in an
unforeseen way. It&#x27;s great!&lt;&#x2F;p&gt;
&lt;p&gt;Contrast this with The White, a series of environmental puzzles that I skipped
in favor of looking up the solutions on the internet. Perhaps this last section
would have had a greater impact if I hadn&#x27;t already played through The Witness.&lt;&#x2F;p&gt;
&lt;p&gt;That said, Taiji is a worthy game in its own right, even if it doesn&#x27;t live up
to the quality of puzzle design in its inspiration, The Witness. Its scope and
price-point are perfection for those looking for a solid puzzle game.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>New Emacs Package: git-share</title>
        <published>2023-04-29T00:00:00+00:00</published>
        <updated>2024-03-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-04-29-store-git-link/"/>
        <id>https://mgmarlow.com/words/2023-04-29-store-git-link/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-04-29-store-git-link/">&lt;p&gt;Today I released an open source Emacs package:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;git-share&#x2F;&quot;&gt;git-share&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This is my first real attempt at a proper Emacs package, bringing together some
scattered elisp scripts I had in my config into a complete, unit-tested whole. I
use this command just about every working day to send code links to colleagues
across our different git repositories.&lt;&#x2F;p&gt;
&lt;p&gt;With Emacs 29 it&#x27;s easy to install the package straight from the repository. Add
these lines to your Emacs config:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;unless&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;package-installed-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;git-share&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;package-vc-install &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;git-share&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Invoke &lt;code&gt;M-x git-share&lt;&#x2F;code&gt; when your Emacs point is positioned on a line of code
you&#x27;d like to share and the link will be copied (killed) to your system&#x27;s
clipboard.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tips-and-tricks&quot;&gt;Tips and Tricks&lt;&#x2F;h2&gt;
&lt;p&gt;Here are some tips I stumbled on when building this package:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;You can generate the comment boilerplate for your Emacs package with
&lt;code&gt;M-x auto-insert&lt;&#x2F;code&gt;. It prompts you for a description and some tags, then fills
in the requisite copyright notice, author fields, and other structural
comments for your package. More info:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;elisp&#x2F;Library-Headers.html&quot;&gt;Conventional Headers for Emacs Libraries&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Unit testing with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_mono&#x2F;ert.html&quot;&gt;ERT&lt;&#x2F;a&gt; is really
straightforward. I use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;git-share&#x2F;tree&#x2F;main&#x2F;item&#x2F;Makefile&quot;&gt;this Makefile&lt;&#x2F;a&gt;
to run the test suite against the compiled (&lt;code&gt;.elc&lt;&#x2F;code&gt;) package.&lt;&#x2F;li&gt;
&lt;li&gt;When browsing for functions with &lt;code&gt;C-h f&lt;&#x2F;code&gt; it&#x27;s easy to accidentally include
ones that aren&#x27;t shipped with Emacs, but are autoloaded by other packages
(notably in my case, string helpers from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;magnars&#x2F;s.el&quot;&gt;s.el&lt;&#x2F;a&gt;). Using ERT batch testing helped me
find these problems since it works against the vanilla Emacs install.&lt;&#x2F;li&gt;
&lt;li&gt;Read the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;elisp&#x2F;Coding-Conventions.html&quot;&gt;Emacs Lisp Coding Conventions&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Even if you&#x27;re not contributing to MELPA, their
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;melpa&#x2F;melpa&#x2F;blob&#x2F;master&#x2F;CONTRIBUTING.org&quot;&gt;contributing guidelines&lt;&#x2F;a&gt;
are a great source of tips.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A Note-taking System for Work</title>
        <published>2023-04-04T00:00:00+00:00</published>
        <updated>2023-04-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-04-04-note-taking-at-work/"/>
        <id>https://mgmarlow.com/words/2023-04-04-note-taking-at-work/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-04-04-note-taking-at-work/">&lt;p&gt;Keeping a code journal has been a gratifying way to measure my professional
growth and keep track of esoteric information. Over the years I&#x27;ve experimented
with &lt;a href=&quot;&#x2F;words&#x2F;2023-03-21-burn-after-writing&quot;&gt;tons of different methods&lt;&#x2F;a&gt;, but code
notes are trickier than word notes and are constrained to a digital environment.&lt;&#x2F;p&gt;
&lt;p&gt;Daily entries are too sparse to be useful, spreading information in a way that
is irrecoverable without a tagging nightmare. Zettelkasten-style is too
nit-picky, exhausting too much of my time in organization and placement. I&#x27;ve
settled on a nice middle ground: a weekly coding scratchpad with backlinks to
longer-form reference material.&lt;&#x2F;p&gt;
&lt;p&gt;Emacs is a great tool for coding notes in particular, thanks to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Literate_programming&quot;&gt;literate programming&lt;&#x2F;a&gt;
environment
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgmode.org&#x2F;worg&#x2F;org-contrib&#x2F;babel&#x2F;intro.html&quot;&gt;org-mode babel&lt;&#x2F;a&gt;. The
same &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgmode.org&#x2F;&quot;&gt;org file&lt;&#x2F;a&gt; that contains my todos also serves as a
dynamic code environment, turning code snippets into executable scripts. Linking
between notes is also easily supported with a lightweight package like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;protesilaos.com&#x2F;emacs&#x2F;denote&quot;&gt;denote&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Together these tools are powerful but not overwhelming. Denote handles naming
conventions for my files so I don&#x27;t need to think about consistency or
placement; org-mode handles markup, tagging, code execution, and todos.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s also very easy to extend. Here&#x27;s the Emacs command I use to generate my
weekly scratchpad, along with its template:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;denote&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; my&#x2F;denote--weekly-template&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;concat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;* Friday&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;- [ ] Retrospective&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;* Thursday&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;* Wednesday&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;* Tuesday&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;* Monday&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;\&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;n&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;          &amp;quot;* Notes&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq denote-templates &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;`((&lt;&#x2F;span&gt;&lt;span&gt;weekly &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;my&#x2F;denote--weekly-template&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span&gt; my&#x2F;denote-weekly&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;Find or create a weekly journal entry.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;interactive&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;let*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; ((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;display-time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;format-time-string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;%G-%U&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;current-time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;         (&lt;&#x2F;span&gt;&lt;span&gt;title &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;concat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;week-&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; display-time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;         (&lt;&#x2F;span&gt;&lt;span&gt;pattern &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;concat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;.*--&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; title&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;         (&lt;&#x2F;span&gt;&lt;span&gt;matches &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;denote-directory-files-matching-regexp pattern&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; matches&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;find-file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;car&lt;&#x2F;span&gt;&lt;span&gt; matches&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;      (&lt;&#x2F;span&gt;&lt;span&gt;denote title &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;journal&amp;quot; &amp;quot;weekly&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;org&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; nil nil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;weekly&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Calling the command &lt;code&gt;my&#x2F;denote-weekly&lt;&#x2F;code&gt; in Emacs will either (a) create a new
denote file with the title &quot;week-YYYY-ww&quot; and the appropriate tags, or (b) open
the existing denote file if one was already created.&lt;&#x2F;p&gt;
&lt;p&gt;The org files themselves end up looking something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#+title:      week-2023-01&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#+date:       [2023-01-02 Mon 08:02]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#+filetags:   :journal:weekly:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#+identifier: 20230101T080211&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;* Friday...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;* TODO Thursday&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- [X] Already done!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;- [ ] Cool coding stuff here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;** Testing scripts for something&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#+begin_src elisp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (message &amp;quot;I was executed with org-babel-execute-src-block!&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#+end_src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#+RESULTS:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;: I was executed with org-babel-execute-src-block!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Occasionally I&#x27;ll need to convert from org to markdown to copy something from my
notes into a work document or task. Calling &lt;code&gt;org-md-export-as-markdown&lt;&#x2F;code&gt; opens up
a new buffer with the entire contents of my weekly note in markdown, ready for
copy+paste.&lt;&#x2F;p&gt;
&lt;p&gt;Org itself is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgmode.org&#x2F;features.html&quot;&gt;rich with features&lt;&#x2F;a&gt; but the
real game-changer for me is the ability to paste code links from disc into an
org file (via &lt;code&gt;org-store-link&lt;&#x2F;code&gt;). Clicking the link will open the source file in
Emacs at the line of code where the link was stored. I used to rely on a similar
workflow by linking to Github (via a custom
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;git-share&quot;&gt;Emacs command&lt;&#x2F;a&gt;), but linking to local
files within Emacs is way better for browsing, editing, and note-taking.&lt;&#x2F;p&gt;
&lt;p&gt;As far as retrieval goes, denote timestamps notes on creation so they&#x27;re neatly
organized in a flat directory:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;~&#x2F;denote&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  20230102T080211--week-2023-01__journal_weekly.org&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  20221228T091238--week-2022-52__journal_weekly.org&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  20221221T083238--week-2022-51__journal_weekly.org&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  20221223T113238--active-record-tips__rails.org&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And since org files are plaintext, you can use org-mode, Emacs, or grep to
search through their contents. Check out
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgmode.org&#x2F;worg&#x2F;org-tutorials&#x2F;advanced-searching.html&quot;&gt;Advanced searching&lt;&#x2F;a&gt;
in the org-mode documentation to see what&#x27;s possible.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Burn After Writing</title>
        <published>2023-03-21T00:00:00+00:00</published>
        <updated>2023-03-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-03-21-burn-after-writing/"/>
        <id>https://mgmarlow.com/words/2023-03-21-burn-after-writing/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-03-21-burn-after-writing/">&lt;p&gt;I think I&#x27;ve experimented with just about every method imaginable for organizing
my writing. Recent trends point me towards zettelkastens, backlinks, and other,
less savory terms like &quot;second brain&quot;. When I spend so much time writing useless
things I feel I owe myself to organize them, lest I miss out on some revelatory
moment that spurs a successful novel. I&#x27;m still waiting.&lt;&#x2F;p&gt;
&lt;p&gt;Despite trying all the flavors, no technique has stuck. Most often the strategy
is too strict; the burden of placing a note in the right place has me forgetting
my original intention for writing it.&lt;&#x2F;p&gt;
&lt;p&gt;The conclusion I&#x27;ve come to is that I&#x27;ve spent far too many hours researching a
topic that is uniquely personal.&lt;&#x2F;p&gt;
&lt;p&gt;My primary goal in writing is to write. Not to denote, tag, or organize. Not to
bibliograph, or space-ly repeat, or architect a knowledge graph. And the best
tool I&#x27;ve found for the job of writing remains pen and paper.&lt;&#x2F;p&gt;
&lt;p&gt;Paper doesn&#x27;t hide mistakes and offers no grammar plugin to help disguise your
voice. Writing by hand is slow and meditative, ensuring each word is adequately
chewed over before it&#x27;s spat out. Best of all, I can stash that thought deep
inside a notebook on my shelf and never have to look at it again.&lt;&#x2F;p&gt;
&lt;p&gt;That shelf houses all of my writings, a bunch of notebooks piled together in
neat stacks. I&#x27;m comforted that I won&#x27;t easily find thoughts from previous me
without digging through them all, one by one. What use are those ideas anyway,
now that I&#x27;ve changed since writing them?&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Anatomy of a Screenplay</title>
        <published>2023-02-11T00:00:00+00:00</published>
        <updated>2023-02-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-02-11-anatomy-of-a-screenplay/"/>
        <id>https://mgmarlow.com/words/2023-02-11-anatomy-of-a-screenplay/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-02-11-anatomy-of-a-screenplay/">&lt;p&gt;Here are some notes I took during
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.futurelearn.com&#x2F;courses&#x2F;screenwriting&quot;&gt;An Introduction to Screenwriting&lt;&#x2F;a&gt;.
The course breaks down the standard film screenplay format and helps
conceptualize the process of writing one yourself.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-pitch&quot;&gt;The pitch&lt;&#x2F;h2&gt;
&lt;p&gt;Before moving from idea to paper you&#x27;ll need to clarify your intentions. Use
these five points as the baseline:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Genre&lt;&#x2F;li&gt;
&lt;li&gt;Main protagonist&lt;&#x2F;li&gt;
&lt;li&gt;Goal&lt;&#x2F;li&gt;
&lt;li&gt;Obstacle&lt;&#x2F;li&gt;
&lt;li&gt;Why this story is important&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Draw special attention to point 5. What makes your story unique, what theme are
you trying to portray? That theme will guide the rest of the screenwriting
process.&lt;&#x2F;p&gt;
&lt;p&gt;Once you&#x27;ve got a good pitch nailed down it&#x27;s time to write your logline.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-logline&quot;&gt;The logline&lt;&#x2F;h2&gt;
&lt;p&gt;Take your pitch and throw it into a one-sentence summary. Here are some
examples:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Apocalypse Now&lt;&#x2F;strong&gt;: During the U.S.-Vietnam War, Captain Willard is sent on a
dangerous mission into Cambodia to assassinate a renegade colonel who has set
himself up as a god among a local tribe.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Matrix&lt;&#x2F;strong&gt;: A computer hacker learns from mysterious rebels about the true
nature of his reality and his role in the war against its controllers.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reservoir Dogs&lt;&#x2F;strong&gt;: After a simple jewelry heist goes terribly wrong, the
surviving criminals begin to suspect that one of them is a police informant.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Don&#x27;t stress out about this step, you&#x27;ll likely tweak and revise as you write.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;flesh-out-your-story&quot;&gt;Flesh out your story&lt;&#x2F;h2&gt;
&lt;p&gt;Build upon your pitch with more details. Film is all about character and change,
use those principles to string together your story beats.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-s-a-beat&quot;&gt;What&#x27;s a beat?&lt;&#x2F;h3&gt;
&lt;p&gt;A story event that transforms the character and story at a critical juncture. A
beat is at the heart of every scene.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;three-act-structure&quot;&gt;Three act structure&lt;&#x2F;h3&gt;
&lt;p&gt;A three-act story maps to a 25-50-25 rhythm, delivering a 100 minute screentime.
The broad strokes of a three act structure are as follows:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Protagonist and goal introduction (25min)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Introduce the setting, genre, characters, themes, and protagonist. Set the tone
for how the audience should consume the movie.&lt;&#x2F;p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;What&#x27;s the obstacle? (50min)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Obstacles force the character into new situations, relationships, and
circumstances.&lt;&#x2F;p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Resolve the story (25min)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Does the protagonist get what they want? How have they changed?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;save-the-cat-beat-sheet&quot;&gt;Save the Cat Beat Sheet&lt;&#x2F;h3&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;savethecat.com&#x2F;&quot;&gt;Save the Cat Beat Sheet&lt;&#x2F;a&gt; is a well-known
boilerplate structure for your average screenplay. Take bits and pieces of it to
help structure your story beats.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Opening image: make the look &amp;amp; feel of your story immediately apparent.&lt;&#x2F;li&gt;
&lt;li&gt;Theme stated: communicate your story&#x27;s theme. Often in a way that your
protagonist doesn&#x27;t quite grasp.&lt;&#x2F;li&gt;
&lt;li&gt;Set-up: establish setting, character, status quo. Who is your character now
so that we can recognize their change?&lt;&#x2F;li&gt;
&lt;li&gt;Catalyst: disrupt your protagonist&#x27;s status quo. They may not be ready to
make a choice, but the story doesn&#x27;t wait.&lt;&#x2F;li&gt;
&lt;li&gt;Debate: the protagonist has doubts about leaving routine behind.&lt;&#x2F;li&gt;
&lt;li&gt;Break into act II: it&#x27;s time to set out on adventure.&lt;&#x2F;li&gt;
&lt;li&gt;B story: subplots ensure, often concerning romance.&lt;&#x2F;li&gt;
&lt;li&gt;Fun and games: the protagonist has fun in their new reality.&lt;&#x2F;li&gt;
&lt;li&gt;Midpoint: the protagonist closes in on their goal.&lt;&#x2F;li&gt;
&lt;li&gt;Bad guys close in: the protagonist may be close, but antagonist
forces&#x2F;obstacles don&#x27;t make it easy.&lt;&#x2F;li&gt;
&lt;li&gt;All is lost: dire circumstances lead to loss.&lt;&#x2F;li&gt;
&lt;li&gt;Dark night of the soul: the protagonist grapples with change and reminisces
on their old reality. Hope is lost.&lt;&#x2F;li&gt;
&lt;li&gt;Break into act III: with misery comes a revival.&lt;&#x2F;li&gt;
&lt;li&gt;Finale: climatic resolution of conflict.&lt;&#x2F;li&gt;
&lt;li&gt;Final image: a bookend that re-invokes the central theme.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;writing-the-script&quot;&gt;Writing the script&lt;&#x2F;h2&gt;
&lt;p&gt;Time to put your hard work to paper.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Write down your pitch.&lt;&#x2F;li&gt;
&lt;li&gt;Develop a logline.&lt;&#x2F;li&gt;
&lt;li&gt;Draft up a summary (a few pages in length).&lt;&#x2F;li&gt;
&lt;li&gt;Lay out the major story beats (~15). Index cards are a great tool for this
purpose, one beat per card.&lt;&#x2F;li&gt;
&lt;li&gt;Write your scenes.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;screenwriting-and-film&quot;&gt;Screenwriting and film&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s nothing revolutionary here but it&#x27;s interesting to see the guts of a
movie exposed. Where do films deviate from these structures, and to what effect?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;The_Hero_with_a_Thousand_Faces&quot;&gt;Campbellian archetypes&lt;&#x2F;a&gt;
tell us that storytelling is fundamental to humanity and that the stories
themselves often map to a common structure. It&#x27;s interesting to explore such
structures and relate them to our own experience. Whether or not the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Hero%27s_journey&quot;&gt;monomyth&lt;&#x2F;a&gt; is the beginning and
end of storytelling doesn&#x27;t dismiss its ubiquity in modern media.&lt;&#x2F;p&gt;
&lt;p&gt;Here we see the same base structures in the form of the Save the Cat Beat Sheet.
Perhaps part of the enjoyment we get from watching movies and listening to
stories is the recognition of common patterns, conflicts, and themes. I suppose
more generally that&#x27;s a trait in all art. Patterns are comforting in their
familiarity, alarming in subversion.&lt;&#x2F;p&gt;
&lt;p&gt;Recognizing these meta-structures is one of my favorite techniques of analysis.
With a good understanding of patterns and archetypes a story can be broken into
its base components. Those components reveal the author&#x27;s intent, emphasizing
deviations and reinforcing themes.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Nostalgia for the Early Web</title>
        <published>2023-02-06T00:00:00+00:00</published>
        <updated>2023-02-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-02-06-early-web-nostalgia/"/>
        <id>https://mgmarlow.com/words/2023-02-06-early-web-nostalgia/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-02-06-early-web-nostalgia/">&lt;p&gt;Although it was a bit before my time I still have nostalgia for the early web.
You know, that time when everyone still used rainbow
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;HTML&#x2F;Element&#x2F;marquee#examples&quot;&gt;marquees&lt;&#x2F;a&gt;
and site-visit counters. Sure, the early web was ugly compared to our modern
minimalist leanings, but it was also fresh and original and full of useless
curios.&lt;&#x2F;p&gt;
&lt;p&gt;My fondest memories of the early web are probably those spent browsing
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;StumbleUpon&quot;&gt;StumbleUpon&lt;&#x2F;a&gt;, the website slot
machine. I&#x27;d click aimlessly and travel the world for hours, reading high school
drama or learning sailing knots or sending &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zombo.com&#x2F;&quot;&gt;zombocom vibes&lt;&#x2F;a&gt;
to my friends.&lt;&#x2F;p&gt;
&lt;p&gt;So many people used personal websites to air their emotions to the world and
broadcast their cool hobbies. It might&#x27;ve been twice the words that can fit in a
tweet but it was also twice the personality. The web at this era wasn&#x27;t the
place for your office suite, and it hadn&#x27;t yet attained the status of &quot;useful&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;I think the modern web lost something in the transition away from these
self-hosted blogs. Projects like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;theuselessweb.com&#x2F;&quot;&gt;theuselessweb.com&lt;&#x2F;a&gt;
can emulate that sort of creative energy, but the web as a whole is lacking in
it.&lt;&#x2F;p&gt;
&lt;p&gt;Things look particularly dire when you come across unfriendly patterns so often:
subscription pop-up modals with microscopic dismissal buttons, SEO spam
cluttering up the first page of Google results, invasive advertisements and
autoplaying videos (thanks Fandom). Browsing websites randomly is a recipe for
disaster. Instead, we rely on centralized platforms like Twitter, Medium, or
Substack, all complete with their own set of issues and worried foremost about
monetization.&lt;&#x2F;p&gt;
&lt;p&gt;Recently I discovered the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gemini.circumlunar.space&#x2F;&quot;&gt;Gemini Protocol&lt;&#x2F;a&gt;,
the origin of my recent wave of early web nostalgia. It&#x27;s an alternative to the
web, built around a protocol that is inherently limiting. Gemini restricts
content to just text and links, no JavaScript, CSS, or HTML5.&lt;&#x2F;p&gt;
&lt;p&gt;Exploring Gemini servers has been a delightful experience. For example, the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;midnight.pub&#x2F;&quot;&gt;Midnight Pub&lt;&#x2F;a&gt; is a tiny message board themed around its
namesake, full of interesting posts. Some are in character, most are just
regular blogs.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The city is buzzing, the streets are like arteries. You see an intriguing
place in the alley, with a moon on its door. It reads &quot;The Midnight Pub&quot;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;There&#x27;s something raw and emotional and even voyeuristic about the types of blog
posts that you find on Gemini. A lot of them channel the feelings I have towards
my early experience with the web, or the modern re-telling of something like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.hypnospace.net&#x2F;&quot;&gt;Hypnospace Outlaw&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Although I don&#x27;t have any faith that Gemini will replace the web, nor would I
particularly want it to, I welcome it as an interesting alternative. If you&#x27;re
interested in checking it out, read the guide over at
&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;geminiquickst.art&#x2F;&quot;&gt;geminiquickst.art&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Emacs 29 Quick Start</title>
        <published>2023-01-18T00:00:00+00:00</published>
        <updated>2024-04-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-01-18-emacs-29-quick-start/"/>
        <id>https://mgmarlow.com/words/2023-01-18-emacs-29-quick-start/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-01-18-emacs-29-quick-start/">&lt;blockquote&gt;
&lt;p&gt;Update: I created &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&quot;&gt;Start Emacs&lt;&#x2F;a&gt; as an
easier way to get up and running with Emacs. It follows many of the same
principles in this guide and throws in some extras.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&quot;&gt;Check it out here&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The no-nonsense guide to getting started with Emacs.&lt;&#x2F;p&gt;
&lt;p&gt;By the end of this guide you&#x27;ll have Emacs 29 configured with better default
settings, IDE features like code-completion and LSP support, and much improved
minibuffer completion. Everything listed here is available out of the box or
downloaded from the default Emacs package server,
&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;elpa.gnu.org&#x2F;&quot;&gt;GNU Elpa&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;&#x2F;h2&gt;
&lt;p&gt;Begin by installing Emacs 29 for your OS:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Mac OS: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;d12frosted&#x2F;homebrew-emacs-plus&quot;&gt;emacs-plus&lt;&#x2F;a&gt; via
Homebrew&lt;&#x2F;li&gt;
&lt;li&gt;Linux: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.savannah.gnu.org&#x2F;cgit&#x2F;emacs.git&quot;&gt;Build from source&lt;&#x2F;a&gt; (see
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.savannah.gnu.org&#x2F;cgit&#x2F;emacs.git&#x2F;tree&#x2F;INSTALL&quot;&gt;INSTALL&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Windows: Download an
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;alpha.gnu.org&#x2F;gnu&#x2F;emacs&#x2F;pretest&#x2F;windows&#x2F;emacs-29&#x2F;?C=M;O=D&quot;&gt;alpha.gnu.org snapshot&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It&#x27;s important to use Emacs 29+ and not a prior version. Emacs 29 ships with two
important libraries (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;joaotavora&#x2F;eglot&quot;&gt;eglot&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jwiegley&#x2F;use-package&quot;&gt;use-package&lt;&#x2F;a&gt;) that are used
extensively in this guide.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;run-through-the-tutorial&quot;&gt;Run through the tutorial&lt;&#x2F;h2&gt;
&lt;p&gt;Go ahead and launch Emacs. You&#x27;re greeted with the startup screen which presents
a bunch of useful information and a dated logo. Of particular note is the Emacs
tutorial, which you should click on before continuing with the rest of this
guide.&lt;&#x2F;p&gt;
&lt;p&gt;You can also launch the tutorial via &lt;code&gt;C-h t&lt;&#x2F;code&gt;.
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;tour&#x2F;&quot;&gt;What is &lt;code&gt;C-h&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;?)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;settings&quot;&gt;Settings&lt;&#x2F;h2&gt;
&lt;p&gt;You&#x27;ve installed Emacs and you know some basic commands. It&#x27;s time to edit your
emacs configuration file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;C-x C-f ~&#x2F;.emacs.d&#x2F;init.el&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Drop in the following
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;eintr&#x2F;index.html&quot;&gt;Emacs Lisp&lt;&#x2F;a&gt;
code:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Hide UI&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;menu-bar-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;tool-bar-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;scroll-bar-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Better default modes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;electric-pair-mode&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;show-paren-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;setq-default&lt;&#x2F;span&gt;&lt;span&gt; indent-tabs-mode &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;nil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;save-place-mode&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;savehist-mode&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;recentf-mode&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;global-auto-revert-mode&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Better default settings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;uniquify&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq uniquify-buffer-name-style &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;forward&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      window-resize-pixelwise t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      frame-resize-pixelwise t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      load-prefer-newer t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      backup-by-copying t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      custom-file &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;expand-file-name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;custom.el&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; user-emacs-directory&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;prog-mode-hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;display-line-numbers-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Refresh package archives (GNU Elpa)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;package&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;unless&lt;&#x2F;span&gt;&lt;span&gt; package-archive-contents&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;package-refresh-contents&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;m not going to walk through each line of code here, you can do that yourself
with the built-in Emacs help system. Use &lt;code&gt;M-x describe-function&lt;&#x2F;code&gt; or
&lt;code&gt;M-x describe-variable&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M-x describe-function menu-bar-mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M-x describe-variable window-resize-pixelwise&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or equivalently,&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;C-h f menu-bar-mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;C-h v window-resize-pixelwise&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s best to get acquainted with the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Help.html&quot;&gt;Emacs help system&lt;&#x2F;a&gt;
and learn how to find help within Emacs itself. Later on you&#x27;ll install
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;minad&#x2F;vertico&quot;&gt;vertico&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;minad&#x2F;marginalia&quot;&gt;marginalia&lt;&#x2F;a&gt;, two packages that make
navigating the minibuffer for commands (like &lt;code&gt;M-x&lt;&#x2F;code&gt;) much more enjoyable.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;packages&quot;&gt;Packages&lt;&#x2F;h2&gt;
&lt;p&gt;With those settings out of the way, we&#x27;re going to install some packages. All of
these packages are available on the default Emacs package server,
&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;elpa.gnu.org&#x2F;&quot;&gt;GNU Elpa&lt;&#x2F;a&gt;. If you&#x27;d like to configure alternatives, like
MELPA, consult &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;melpa.org&#x2F;#&#x2F;getting-started&quot;&gt;the docs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Great looking theme&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; modus-themes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;modus-themes-load-themes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;modus-themes-load-vivendi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Code completion at point&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; company&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;after-init &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt; global-company-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;custom&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;company-idle-delay &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Better minibuffer completion&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; vertico&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;custom&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;vertico-cycle t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;read-buffer-completion-ignore-case&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;read-file-name-completion-ignore-case&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;completion-styles&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;(&lt;&#x2F;span&gt;&lt;span&gt;basic substring partial-completion flex&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;vertico-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Save minibuffer results&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; savehist&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;savehist-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Show lots of useful stuff in the minibuffer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; marginalia&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;after vertico&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;marginalia-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;&#x2F;h2&gt;
&lt;p&gt;Save your configuration file, close and re-open Emacs. Time to experiment!&lt;&#x2F;p&gt;
&lt;p&gt;If you have an LSP server already installed, e.g. solargraph for Ruby, browse to
a source file and activate &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;joaotavora.github.io&#x2F;eglot&#x2F;&quot;&gt;eglot&lt;&#x2F;a&gt; with
&lt;code&gt;M-x eglot&lt;&#x2F;code&gt;. You can ensure this happens automatically by adding an
&lt;code&gt;eglot-ensure&lt;&#x2F;code&gt; hook to your Emacs configuration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; eglot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;hook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;ruby-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span&gt; eglot-ensure&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From here, it&#x27;s really up to you to explore and learn on your own. Here are some
suggestions to help you along:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Read about
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Basic.html&quot;&gt;Basic Editing Commands&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Read about
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Projects.html&quot;&gt;Working with Projects&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Read about
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Dired.html&quot;&gt;Dired, the Directory Editor&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Browse my other &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.mgmarlow.com&#x2F;tags&#x2F;emacs&#x2F;&quot;&gt;Emacs posts&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Check out Mickey Peterson&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.masteringemacs.org&#x2F;&quot;&gt;Mastering Emacs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Case Study: Zach Gage</title>
        <published>2023-01-15T00:00:00+00:00</published>
        <updated>2023-01-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2023-01-15-case-study-zach-gage/"/>
        <id>https://mgmarlow.com/words/2023-01-15-case-study-zach-gage/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2023-01-15-case-study-zach-gage/">&lt;p&gt;Lately I&#x27;ve been playing a ton of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;playknotwords.com&#x2F;&quot;&gt;Knotwords&lt;&#x2F;a&gt;, an
intensely clever puzzle game that is conceptually distinct from crossword
puzzles but rides some of the same highs and lows. The game was created by Zach
Gage and Jack Schlesinger, a duo behind several other standout App Store hits
that carry many aesthetic and conceptual similarities.&lt;&#x2F;p&gt;
&lt;p&gt;With such a strong &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;stfj.net&#x2F;apps&#x2F;index.html&quot;&gt;portfolio&lt;&#x2F;a&gt;, I figured that
there must be something I can learn by digging a bit into Zach Gage&#x27;s interview
and GDC material. What I came away with are five or so design principles that
are present in some of his more recent games.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;subway-legibility&quot;&gt;Subway legibility&lt;&#x2F;h2&gt;
&lt;p&gt;A term coined in his
&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;stfj.net&#x2F;DesigningForSubwayLegibility&#x2F;&quot;&gt;GDC 2018 talk&lt;&#x2F;a&gt;, subway
legibility is the ability for someone to understand a game by observing it over
the shoulder of someone else playing it on the subway.&lt;&#x2F;p&gt;
&lt;p&gt;Many of Zach&#x27;s aesthetic design decisions are based on this principle,
communicating information in such a way that a quick glance exposes the core
game mechanisms. The onlooker should be intrigued by the puzzle presented on the
screen, as opposed to overwhelmed by clutter and detail.&lt;&#x2F;p&gt;
&lt;p&gt;This principle is the enemy of a busy user interface (UI). Zach breaks game UIs
down with a method called the &quot;three reads&quot;:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The first glance should convey the important bits, pulling you in.&lt;&#x2F;li&gt;
&lt;li&gt;The second glance should add supporting detail without overwhelming.&lt;&#x2F;li&gt;
&lt;li&gt;The third fills in the rest.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The designer can employ techniques of font size, UI composition, and point of
view to help reinforce the three reads.&lt;&#x2F;p&gt;
&lt;p&gt;Successful legibility exposes the most important details first and foremost. In
Knotwords, this is the arrangement of words and letters in the grid. Secondary
are key details that support the core game components. To follow the same
example, the dotted-lines and letter superscripts that denote the Knotwords
puzzle constraint. Finally are necessities that are unimportant to the gameplay,
e.g. the main menu button.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;exploratory-design-process&quot;&gt;Exploratory design process&lt;&#x2F;h2&gt;
&lt;p&gt;In his
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gamedeveloper.com&#x2F;playdate-launch&#x2F;from-snake-to-snak-indie-developer-zach-gage-on-creating-for-playdate&quot;&gt;Playdate developer interview&lt;&#x2F;a&gt;,
Zach discusses his exploratory design process.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Usually the experience I have is I&#x27;m playing a game, and one cool thing
happens--or, one cool thing almost happens. Then it&#x27;s like &#x27;oh, is there a way
I could&#x27;ve engineered that within the mechanics?&#x27;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The gist of the interview is leaving yourself enough room to explore game
mechanics that emerge from gameplay, mechanics that often lead you down
completely unanticipated avenues. When these surprising moments are discovered
during the design process, make sure to double-down on them and expose them to
the player.&lt;&#x2F;p&gt;
&lt;p&gt;This idea reminds a lot of the advice given by Jonathan Blow and Marc Ten Bosch
in their &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=OGSeLSmOALU&quot;&gt;IndieCade 2011 talk&lt;&#x2F;a&gt;.
They title the presentation &quot;Designing to Reveal The Nature of the Universe&quot; and
the central idea is much the same as what Zach discusses in the above quote.&lt;&#x2F;p&gt;
&lt;p&gt;When designing levels for your game, there will inevitably be situations where
unanticipated interactions arise and expose interesting extensions of your core
game ideas. When these moments do arise, it&#x27;s critical to invest yourself fully
in uncovering what spurred on the interest and excitement. That epiphany is the
kernel of good level design, and it&#x27;s the level designer&#x27;s responsibility to
demonstrate it to the player and deliver the same euphoric effect.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gesture-forward-controls&quot;&gt;Gesture-forward controls&lt;&#x2F;h2&gt;
&lt;p&gt;Another consistent quality of Zach&#x27;s games is attention to control mechanisms
that are unique to the mobile platform. For example, in Knotwords there&#x27;s a
great usability feature that hinges on a directional swipe. Swipe horizontally
from your current square and letters are entered left-right. Swipe vertically
and letters are entered top-bottom.&lt;&#x2F;p&gt;
&lt;p&gt;For a game that is fundamentally about typing letters into an asymmetrical grid,
swapping between left-right and top-down is a critical feature. It&#x27;s hard to
explain how this little gesture changes the overall usability of the game,
unless you have some experience with the NYT Crossword app.&lt;&#x2F;p&gt;
&lt;p&gt;In NYT Crossword you swap letter direction by tapping on your current square.
Since a tap simply toggles between two states, if you tap one time too many you
effectively undo the change. This leads to a very frustrating experience where
you enter in a stream of letters in the wrong direction because you fat-fingered
a square.&lt;&#x2F;p&gt;
&lt;p&gt;With Knotwords, there&#x27;s no way to fall into this problem because the swipe is
directional--swiping twice in the same direction makes no difference. This is
just one example of the many unique control mechanisms in Zach&#x27;s games, each
designed to work around common usability issues.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unobtrusive-tutorialization&quot;&gt;Unobtrusive tutorialization&lt;&#x2F;h2&gt;
&lt;p&gt;The first time I played through &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.playgoodsudoku.com&#x2F;&quot;&gt;Good Sudoku&lt;&#x2F;a&gt; I
was extremely impressed by the tutorialization. Most textual explanations are
tucked away in little icons next to their respective control. If you interact
with the icon, you trigger the tutorial. Otherwise you can just play the game.&lt;&#x2F;p&gt;
&lt;p&gt;I imagine this design is heavily catered to the mobile game market. With so many
freemium apps competing for users, developers want to ensure their app holds the
user&#x27;s attention past the 30-second first-time user experience. Obstructive
tutorials and unskippable videos are probably the top two reasons I will
immediately close an app, never to play it again.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, it&#x27;s entirely possible that no tutorials are just as bad,
particularly if a user is dropped into the middle of a game they have no idea
how to play. I think Zach&#x27;s UI design and &quot;subway legibility&quot; do a lot of heavy
lifting in this space. Designs that are easy to read serve a dual purpose: they
capture the user&#x27;s attention and they communicate core game mechanics. Without
good legibility, it would be much harder to belay tutorials in favor of player
exploration.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;elevator-pitch&quot;&gt;Elevator pitch&lt;&#x2F;h2&gt;
&lt;p&gt;When you first open up Knotwords or Good Sudoku, you&#x27;re presented with a short
textual blurb from Zach describing his design goals and the game&#x27;s origin. It&#x27;s
a nice, personalized message that helps introduce the player to the game while
emphasizing its uniqueness.&lt;&#x2F;p&gt;
&lt;p&gt;Not only does this set Zach&#x27;s apps apart from others in the App Store, it
demonstrates the clarity of game design carried throughout the development
process. The message strikes me as a confident appeal: the game in your hands
isn&#x27;t a product of random chance and a lucky idea, but of careful design and
focused effort.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;There&#x27;s probably more to unpack from Zach Gage&#x27;s games, but this post summarizes
what I&#x27;ve been thinking about while playing Knotwords and Good Sudoku. These
games are certainly small in scope and heavily rooted in the puzzle genre, but I
think that the core principles are generally applicable to game designers of all
types.&lt;&#x2F;p&gt;
&lt;p&gt;While Knotwords and Good Sudoku are simple games, it&#x27;s too easy to dismiss
simplicity as an easy road while missing the long path of careful design that
came before it.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Advent of Code with Common Lisp</title>
        <published>2022-12-19T00:00:00+00:00</published>
        <updated>2022-12-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-12-17-advent-of-code/"/>
        <id>https://mgmarlow.com/words/2022-12-17-advent-of-code/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-12-17-advent-of-code/">&lt;p&gt;One observation I&#x27;ve had working through
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;adventofcode.com&#x2F;&quot;&gt;Advent of Code&lt;&#x2F;a&gt; with Common Lisp is that the &lt;code&gt;LOOP&lt;&#x2F;code&gt;
macro is an absolute powerhouse.&lt;&#x2F;p&gt;
&lt;p&gt;When first learning Common Lisp, it&#x27;s common to hear that the language is
actually comprised of three separate languages: Common Lisp, &lt;code&gt;FORMAT&lt;&#x2F;code&gt;, and
&lt;code&gt;LOOP&lt;&#x2F;code&gt;. Common Lisp itself is made up of the parenthetical soup that is easily
recognizable. &lt;code&gt;FORMAT&lt;&#x2F;code&gt; and &lt;code&gt;LOOP&lt;&#x2F;code&gt;, on the other hand, each have their own
bespoke syntax that looks next to nothing like Lisp.&lt;&#x2F;p&gt;
&lt;p&gt;Although the unique syntax of &lt;code&gt;FORMAT&lt;&#x2F;code&gt; and &lt;code&gt;LOOP&lt;&#x2F;code&gt; brings a learning curve on top
of Common Lisp itself, both tools bring an incredible amount of power to the
language. &lt;code&gt;LOOP&lt;&#x2F;code&gt; in particular has been fantastically useful in this year&#x27;s
Advent of Code.&lt;&#x2F;p&gt;
&lt;p&gt;Most Advent of Code exercises involve (a) reading lines of data from a file and
(b) accumulating or counting some result from the data. This sort of operation
is handled very gracefully by &lt;code&gt;LOOP&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a simple example from Day 2, summing scores from a file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; score&lt;&#x2F;span&gt;&lt;span&gt; (line) ...)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;with-open-file&lt;&#x2F;span&gt;&lt;span&gt; (in &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;day02.txt&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;loop&lt;&#x2F;span&gt;&lt;span&gt; for line &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;read-line&lt;&#x2F;span&gt;&lt;span&gt; in &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;nil&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        while line&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        summing (score line) into total&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        finally (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; total)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Within the &lt;code&gt;LOOP&lt;&#x2F;code&gt; macro, I&#x27;m reading one line from the input file at a time,
storing it into the variable, &lt;code&gt;LINE&lt;&#x2F;code&gt;. After checking that the &lt;code&gt;LINE&lt;&#x2F;code&gt; is
non-empty or EOF, I pass it into the &lt;code&gt;SCORE&lt;&#x2F;code&gt; function. The result of that
function call is accumulated into a variable, &lt;code&gt;TOTAL&lt;&#x2F;code&gt;, which is incremented on
each iteration of the loop. Finally, once the entire file is processed (and
&lt;code&gt;LINE&lt;&#x2F;code&gt; is &lt;code&gt;NIL&lt;&#x2F;code&gt;) I return &lt;code&gt;TOTAL&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Not very &quot;Lispy&quot;, but very expressive and readable.&lt;&#x2F;p&gt;
&lt;p&gt;Day 12 has a more complex example. The following loops over all possible
starting positions for a graph (the input) and determines the path to the given
endpoint via a breadth-first search. While collecting these paths, the macro
saves the shortest path into a variable, &lt;code&gt;MIN&lt;&#x2F;code&gt;, then returns it as the result.
This variable is compared against all iterations, only changing value if the
iteration is less than the stored result.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;defun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; bfs&lt;&#x2F;span&gt;&lt;span&gt; (start end) ...)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;loop&lt;&#x2F;span&gt;&lt;span&gt; for start in &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;*starting-positions*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      for path &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; (bfs start &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;#\E&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      when&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;not&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;null&lt;&#x2F;span&gt;&lt;span&gt; path))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      minimizing (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; path) into &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;min&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      finally (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;1- min&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that you can assign as many iterators as you want by adding additional
&lt;code&gt;for VAR = EXPRESSION&lt;&#x2F;code&gt;. You can similarly accumulate iterators from an
automatically incremented value, like an index: &lt;code&gt;for VAR from 0&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Another cool feature is the ability to loop over successive &lt;code&gt;CDR&lt;&#x2F;code&gt;s of a list.
This feature was super handy for Day 13, when I needed to zip together pairs
from a list of inputs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;loop&lt;&#x2F;span&gt;&lt;span&gt; for el on &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;(a b c d e f)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      if (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;&amp;gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; el) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;mod&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;length&lt;&#x2F;span&gt;&lt;span&gt; el) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        collect (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;subseq&lt;&#x2F;span&gt;&lt;span&gt; el &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0 2&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Result: ((A B) (C D) (E F))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When looping via &lt;code&gt;on&lt;&#x2F;code&gt;, the value bound into &lt;code&gt;EL&lt;&#x2F;code&gt; is the &lt;code&gt;CDR&lt;&#x2F;code&gt; of that list,
similar to calling &lt;code&gt;NTHCDR&lt;&#x2F;code&gt; with the index of that iteration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;nthcdr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0 &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;(a b c d))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;#39;(a b c d)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;nthcdr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1 &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;(a b c d))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;#39;(b c d)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;nthcdr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2 &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;(a b c d))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;#39;(c d)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By collecting &lt;code&gt;SUBSEQ&lt;&#x2F;code&gt;, I&#x27;m only collecting a slice of the list from the
starting index to the end, forming pairs of two.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;subseq&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;(a b c d) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0 2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; &amp;#39;(a b)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Combine these two things, and protect against out-of-index errors, and pairs are
achieved. A two-length slice is collected on each iteration from consecutive
&lt;code&gt;CDR&lt;&#x2F;code&gt;s of the list.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;LOOP&lt;&#x2F;code&gt; rabbit hole goes far deeper than what&#x27;s shown in this post. A chapter
of Peter Seibel&#x27;s &lt;em&gt;Practical Common Lisp&lt;&#x2F;em&gt; has an entire chapter dedicated to the
macro, the aptly named
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gigamonkeys.com&#x2F;book&#x2F;loop-for-black-belts.html&quot;&gt;LOOP for Black Belts&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Why not Doom Emacs?</title>
        <published>2022-11-12T00:00:00+00:00</published>
        <updated>2022-11-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-11-11-why-emacs-from-scratch/"/>
        <id>https://mgmarlow.com/words/2022-11-11-why-emacs-from-scratch/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-11-11-why-emacs-from-scratch/">&lt;p&gt;The thing I credit most for sticking with Emacs after several failed attempts is
building my own configuration [from
scratch]({{ &#x27;&#x2F;words&#x2F;2022-05-02-learning-emacs&#x27; | url }}). Not only was the
project easier than expected, it left me with the distinctive fullness created
by the product of joyful work.&lt;&#x2F;p&gt;
&lt;p&gt;My previous experiments with frameworks like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;doomemacs&#x2F;doomemacs&quot;&gt;Doom Emacs&lt;&#x2F;a&gt; or
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.spacemacs.org&#x2F;&quot;&gt;Spacemacs&lt;&#x2F;a&gt; were dropped shortly after the first
sign of trouble. It was hard not to feel overwhelmed by the complexity of the
ecosystem and frustrated by my inability to troubleshoot my own problems.&lt;&#x2F;p&gt;
&lt;p&gt;Don&#x27;t get me wrong, Emacs frameworks offer compelling benefits for the lazy
hacker. They pack in loads of sensible defaults, equip you with a sweet-looking
UI, and offer modern language support out-of-the-box. Doom Emacs has over 15k
stars on Github and it&#x27;s not just because of the adorable cacodemon in the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;doomemacs&#x2F;doomemacs#introduction&quot;&gt;README&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That said, if you&#x27;re anything like me you&#x27;ll quickly become overwhelmed after
installing Doom Emacs for the first time. Perhaps your installation doesn&#x27;t
quite work, so you have to fix a few problems with &lt;code&gt;bin&#x2F;doom doctor&lt;&#x2F;code&gt;. Maybe you
want to change your fonts, so you dig through the documentation until you
stumble upon the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;doomemacs&#x2F;doomemacs&#x2F;blob&#x2F;master&#x2F;docs&#x2F;faq.org#change-my-fonts&quot;&gt;FAQ&lt;&#x2F;a&gt;.
Inevitably the framework that you installed to shortcut learning Emacs may not
be as quick and easy as you expected.&lt;&#x2F;p&gt;
&lt;p&gt;Even with frameworks, there is no escaping the need to learn Emacs fundamentals
to configure the editor to match your expectations. However, frameworks make the
process of learning these fundamentals more difficult by greatly increasing the
complexity space of your editor and introducing more points of failure.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s why I recommend Emacs beginners start their journey with vanilla Emacs.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Frameworks front-load learning the framework over learning Emacs&lt;&#x2F;li&gt;
&lt;li&gt;New points of failure make diagnosing problems &quot;the Emacs way&quot; more difficult&lt;&#x2F;li&gt;
&lt;li&gt;Framework defaults may not match your needs and expectations&lt;&#x2F;li&gt;
&lt;li&gt;Modern (28+) vanilla Emacs isn&#x27;t as bad as you think&lt;&#x2F;li&gt;
&lt;li&gt;Emacs principles will serve you well if you adopt a framework later&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you&#x27;re looking to get started with vanilla Emacs, I recommend diving into the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;74zOY-vgkyw&quot;&gt;System Crafters tutorials&lt;&#x2F;a&gt; and kickstarting your
configuration with a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;mgmarlow&#x2F;c298502c0f84d1c06c881b8de404b7c7&quot;&gt;few defaults&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>LSP with Emacs 29</title>
        <published>2022-10-29T00:00:00+00:00</published>
        <updated>2022-12-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-10-23-eglot/"/>
        <id>https://mgmarlow.com/words/2022-10-23-eglot/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-10-23-eglot/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;joaotavora.github.io&#x2F;eglot&#x2F;&quot;&gt;Eglot&lt;&#x2F;a&gt;, an Emacs package that integrates
the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;microsoft.github.io&#x2F;language-server-protocol&#x2F;&quot;&gt;language server protocol&lt;&#x2F;a&gt;
(LSP) into Emacs, was just merged into
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lists.gnu.org&#x2F;archive&#x2F;html&#x2F;emacs-devel&#x2F;2022-10&#x2F;msg01609.html&quot;&gt;Emacs main&lt;&#x2F;a&gt;.
It joins &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacs-tree-sitter.github.io&#x2F;&quot;&gt;tree-sitter&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jwiegley&#x2F;use-package&quot;&gt;use-package&lt;&#x2F;a&gt; as another reason to be
excited about the Emacs 29 release.&lt;&#x2F;p&gt;
&lt;p&gt;This post aims to answer some common questions I observed in recent discussions
around the Eglot merge. In particular, what is LSP and how do alternatives
compare to Eglot?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-is-lsp&quot;&gt;What is LSP?&lt;&#x2F;h2&gt;
&lt;p&gt;LSP was originally designed to provide IDE language features to Visual Studio
Code, e.g., &quot;Go to definition&quot; or &quot;Find references&quot; popups. The project was
later open sourced by Microsoft and now has a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;microsoft.github.io&#x2F;language-server-protocol&#x2F;specifications&#x2F;lsp&#x2F;3.17&#x2F;specification&#x2F;&quot;&gt;formal specification&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The protocol came out of the need to design an editor-specific compiler that
prioritizes the features programmers care about when actively writing code, as
opposed to compilers that work with code that is already written. While such a
compiler can be built on an ad-hoc basis for a specific IDE, it is difficult to
share that work with other editors that may be built using a different
programming language. With LSP, the compiler is run in an
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;learn.microsoft.com&#x2F;en-us&#x2F;visualstudio&#x2F;extensibility&#x2F;language-server-protocol?view=vs-2022#how-the-lsp-works&quot;&gt;independent process&lt;&#x2F;a&gt;
that communicates to the editor via messages, serialized as JSON.&lt;&#x2F;p&gt;
&lt;p&gt;Decoupling the language server implementation from the editor or extension
implementation is a great move that has seen a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;langserver.org&#x2F;#implementations-server&quot;&gt;broad level of adoption&lt;&#x2F;a&gt; in the
editor space, even outside of Visual Studio Code. And now with Emacs 29, we have
support built into Emacs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-was-eglot-merged-instead-of-alternatives&quot;&gt;Why was Eglot merged instead of alternatives?&lt;&#x2F;h2&gt;
&lt;p&gt;Two primary reasons come to mind:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Free Software Foundation (FSF) licensing&lt;&#x2F;li&gt;
&lt;li&gt;Idiomatic Emacs Lisp and package standards&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The former reason refers to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;licenses&#x2F;why-assign.en.html&quot;&gt;GNU requirement&lt;&#x2F;a&gt; that
&quot;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;prep&#x2F;maintain&#x2F;html_node&#x2F;Legally-Significant.html#Legally-Significant&quot;&gt;legally significant&lt;&#x2F;a&gt;&quot;
contributions (e.g. 15+ LOC) are accompanied by signed paperwork. If you&#x27;ve ever
tried contributing to an Emacs package that lives in Emacs main you&#x27;re probably
already familiar with the process of submitting a request, signing the
documents, and sending them in for approval.&lt;&#x2F;p&gt;
&lt;p&gt;Since the nature of this requirement extends to all contributors of an Emacs
package, it can be difficult to gain approval to merge a new package into Emacs
main without requiring copyright from the beginning. Otherwise, package
maintainers will need to hunt down contributors to get them to sign the relevant
paperwork (see
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jwiegley&#x2F;use-package&#x2F;issues&#x2F;282&quot;&gt;use-package#282&lt;&#x2F;a&gt; as an
example).&lt;&#x2F;p&gt;
&lt;p&gt;The secondary reason has to do with Eglot&#x27;s implementation. Compared to
alternatives, Eglot doesn&#x27;t depend on any packages external to Emacs and
integrates nicely with existing Emacs tools like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_mono&#x2F;flymake.html#Top&quot;&gt;Flymake&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_mono&#x2F;emacs.html#Xref&quot;&gt;Xref&lt;&#x2F;a&gt;,
etc. These qualities make it easier to merge and ship with the standard Emacs
distribution.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-do-alternatives-differ&quot;&gt;How do alternatives differ?&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;lsp-mode&quot;&gt;lsp-mode&lt;&#x2F;h3&gt;
&lt;p&gt;The alternative I have the most experience with is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emacs-lsp&#x2F;lsp-mode&quot;&gt;lsp-mode&lt;&#x2F;a&gt;, which I used for six or so
months before switching to Eglot. Unlike Eglot, lsp-mode has
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emacs-lsp&#x2F;lsp-mode&#x2F;issues&#x2F;444&quot;&gt;no intention of being merged into Emacs main&lt;&#x2F;a&gt;,
allowing it some more flexibility in its design and implementation.&lt;&#x2F;p&gt;
&lt;p&gt;Compared to Eglot, lsp-mode is the more maximal package. For one, it supports
the entire LSP specification. It also has a ton of extra features, including
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacs-lsp.github.io&#x2F;lsp-ui&quot;&gt;bespoke UI&lt;&#x2F;a&gt;, non-ELPA package integrations
like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emacs-lsp&#x2F;dap-mode&quot;&gt;dap-mode&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;magnars&#x2F;dash.el&quot;&gt;dash&lt;&#x2F;a&gt;, and support for
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emacs-lsp&#x2F;lsp-mode&#x2F;issues&#x2F;424&quot;&gt;multiple language servers&lt;&#x2F;a&gt;
for a single file.&lt;&#x2F;p&gt;
&lt;p&gt;Whether you decide to pick lsp-mode over Eglot probably comes down to your
reaction to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacs-lsp.github.io&#x2F;lsp-ui&#x2F;&quot;&gt;lsp-ui&lt;&#x2F;a&gt;. If you desire a
fully-featured IDE experience, lsp-mode will serve you well. Otherwise, you may
appreciate Eglot&#x27;s simpler take.&lt;&#x2F;p&gt;
&lt;p&gt;No matter which LSP package you pick, it&#x27;s worth reading through lsp-mode&#x27;s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacs-lsp.github.io&#x2F;lsp-mode&#x2F;page&#x2F;performance&#x2F;&quot;&gt;performance documentation&lt;&#x2F;a&gt;.
It&#x27;s beneficial no matter which LSP package you choose.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;lsp-bridge&quot;&gt;lsp-bridge&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;manateelazycat&#x2F;lsp-bridge&quot;&gt;lsp-bridge&lt;&#x2F;a&gt; aims to be &quot;the
fastest LSP client in Emacs&quot;. Contributor Matthew Zeng recently gave an
introduction to the package at
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacsconf.org&#x2F;2022&#x2F;talks&#x2F;lspbridge&#x2F;&quot;&gt;EmacsConf 2022&lt;&#x2F;a&gt;, walking through
it&#x27;s implementation and goals.&lt;&#x2F;p&gt;
&lt;p&gt;In general, lsp-bridge aims to work around the single-threaded bottlenecks
present in Emacs and Emacs Lisp. Since LSP works via JSON RPC, most of the
performance issues are due to blocking IO and JSON parsing. lsp-bridge splits
processing into two threads to better handle asynchronous communication between
server and editor.&lt;&#x2F;p&gt;
&lt;p&gt;The quest for performance comes with a cost, however, in that lsp-bridge
requires Python dependencies and ties its implementation to a specific
completion framework bundled with the package.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tl-dr&quot;&gt;tl;dr&lt;&#x2F;h2&gt;
&lt;p&gt;Eglot is a rad LSP package that is now merged into Emacs main, staged for
release with Emacs 29. It&#x27;s well worth giving it a try, even if you already use
lsp-mode. You may prefer its comparatively lightweight feel.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;eglot&quot;&gt;Eglot&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;Minimalist implementation that &quot;just works&quot;&lt;&#x2F;li&gt;
&lt;li&gt;Available out of the box with Emacs 29&lt;&#x2F;li&gt;
&lt;li&gt;Development may slow down since it&#x27;s now integrated in Emacs main&lt;&#x2F;li&gt;
&lt;li&gt;No dependencies outside of Emacs&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;lsp-mode-1&quot;&gt;lsp-mode&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;Fully supports LSP specification&lt;&#x2F;li&gt;
&lt;li&gt;Optional UI extras with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacs-lsp.github.io&#x2F;lsp-ui&quot;&gt;lsp-ui&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Debugging support with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacs-lsp.github.io&#x2F;dap-mode&#x2F;&quot;&gt;dap-mode&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Support for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;flycheck&#x2F;flycheck&quot;&gt;Flycheck&lt;&#x2F;a&gt;, a Flymake
alternative&lt;&#x2F;li&gt;
&lt;li&gt;Can be a pain to set up properly&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;lsp-bridge-1&quot;&gt;lsp-bridge&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;Great performance at the cost of idiomatic Emacs conventions&lt;&#x2F;li&gt;
&lt;li&gt;Coupled to a specific completion framework bundled with the package&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reading: The Puzzler</title>
        <published>2022-09-22T00:00:00+00:00</published>
        <updated>2022-09-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-09-22-reading-the-puzzler/"/>
        <id>https://mgmarlow.com/words/2022-09-22-reading-the-puzzler/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-09-22-reading-the-puzzler/">&lt;p&gt;Talking about &lt;em&gt;The Puzzler&lt;&#x2F;em&gt; by A.J. Jacobs.&lt;&#x2F;p&gt;
&lt;p&gt;If you can ignore Jacobs musings on the virtues of puzzling (which is, despite
the book&#x27;s subtitle, mercifully light), &lt;em&gt;The Puzzler&lt;&#x2F;em&gt; offers some entertaining
stories about puzzle fanaticism. Just don&#x27;t go in expecting much else.&lt;&#x2F;p&gt;
&lt;p&gt;Each chapter is about a blog-post&#x27;s worth of content that follows a formula
common to every puzzle in the book. Most interesting are Jacobs&#x27;s explorations
into puzzling esoterica, e.g. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Kryptos&quot;&gt;Kryptos&lt;&#x2F;a&gt;
or the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;puzzles.mit.edu&#x2F;&quot;&gt;MIT Mystery Hunt&lt;&#x2F;a&gt;. Although the formula gets
tiring quickly, Jacobs keeps it alive by offering up historical puzzles that the
reader can peruse on their own terms.&lt;&#x2F;p&gt;
&lt;p&gt;None of what Jacobs provides would be so egregious if his tone and humor weren&#x27;t
representative of the lowest common denominator of &lt;em&gt;The New York Times&lt;&#x2F;em&gt; readers.
The repertoire of jokes at Jacobs disposal is seemingly limited to the political
and social circumstances of 2020, a time I&#x27;d much rather forget than reminisce
with the author&#x27;s patronizing guidance.&lt;&#x2F;p&gt;
&lt;p&gt;Moreover, his grand ideas on the applicability of puzzles to everyday life are
marred by his inclusions of &quot;nerd culture&quot; stereotypes. At one point Jacobs asks
a Rubik&#x27;s Cube aficionado whether his &quot;Rubik&#x27;s Cube renown led to him dating the
head cheerleader&quot; (31).&lt;&#x2F;p&gt;
&lt;p&gt;Ultimately &lt;em&gt;The Puzzler&lt;&#x2F;em&gt; is a disappointing read. Perhaps if the book were
instead a series of blog posts I would be more forgiving to its content and
meandering lack of purpose. But viewed as a whole, the book doesn&#x27;t justify its
own existence.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;&#x2F;strong&gt;: read &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rexwordpuzzle.blogspot.com&#x2F;&quot;&gt;Rex Parker&lt;&#x2F;a&gt; instead.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building Emacs from source on MacOS</title>
        <published>2022-09-08T00:00:00+00:00</published>
        <updated>2022-10-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-09-08-building-emacs-mac-os/"/>
        <id>https://mgmarlow.com/words/2022-09-08-building-emacs-mac-os/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-09-08-building-emacs-mac-os/">&lt;p&gt;This is a guide for building Emacs from source for Mac OSX (tested on 12.4, M1)
with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.masteringemacs.org&#x2F;article&#x2F;speed-up-emacs-libjansson-native-elisp-compilation&quot;&gt;native compilation&lt;&#x2F;a&gt;
enabled. If you don&#x27;t want native compilation (though I highly recommend it),
feel free to drop the the &lt;code&gt;--with-native-compilation&lt;&#x2F;code&gt; flag when you run the
&lt;code&gt;.&#x2F;configure&lt;&#x2F;code&gt; script.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Xcode command line tools&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;formulae.brew.sh&#x2F;formula&#x2F;libgccjit&quot;&gt;&lt;code&gt;libgccjit&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, required for native
compilation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;building-emacs&quot;&gt;Building Emacs&lt;&#x2F;h2&gt;
&lt;p&gt;First, clone the repo (you can also use the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emacs-mirror&#x2F;emacs&quot;&gt;Github mirror&lt;&#x2F;a&gt; instead):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;git clone https:&#x2F;&#x2F;git.savannah.gnu.org&#x2F;git&#x2F;emacs.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These next few steps are taken straight from the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emacs-mirror&#x2F;emacs&#x2F;blob&#x2F;master&#x2F;INSTALL.REPO&quot;&gt;INSTALL.REPO&lt;&#x2F;a&gt;
file in the source repository, with the addition of a few options during
configuration.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;run-autogen&quot;&gt;Run autogen&lt;&#x2F;h3&gt;
&lt;p&gt;After the code is pulled down, cd into the directory and run the &lt;code&gt;autogen.sh&lt;&#x2F;code&gt;
script. This initial script generates another script (&lt;code&gt;configure&lt;&#x2F;code&gt;) that you&#x27;ll
use to actually configure the Emacs &lt;code&gt;Makefile&lt;&#x2F;code&gt; to build on your OS.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cd emacs&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.&#x2F;autogen.sh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;run-configure&quot;&gt;Run configure&lt;&#x2F;h3&gt;
&lt;p&gt;You can view all of the available options for &lt;code&gt;configure&lt;&#x2F;code&gt; by passing in the
&lt;code&gt;--help&lt;&#x2F;code&gt; flag:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.&#x2F;configure --help&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here is the list of options that are recommended for Mac OS, compiled from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;d12frosted&#x2F;homebrew-emacs-plus&quot;&gt;various&lt;&#x2F;a&gt;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mclear-tools&#x2F;build-emacs-macos&quot;&gt;sources&lt;&#x2F;a&gt;. I&#x27;ve included the
&lt;code&gt;--help&lt;&#x2F;code&gt; output with each option:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--with-native-compilation&lt;&#x2F;code&gt;: compile with Emacs Lisp native compiler support&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--with-json&lt;&#x2F;code&gt;: compile with native JSON support&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--with-ns&lt;&#x2F;code&gt;: use Nextstep (macOS Cocoa or GNUstep) windowing system. On by
default on macOS&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--with-xwidgets&lt;&#x2F;code&gt;: enable use of xwidgets in Emacs buffers (requires macOS
Cocoa,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;elisp&#x2F;Xwidgets.html&quot;&gt;more info&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--without-dbus&lt;&#x2F;code&gt;: don&#x27;t compile with D-Bus support
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_mono&#x2F;dbus.html&quot;&gt;more info&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--without-compress-install&lt;&#x2F;code&gt;: don&#x27;t compress some files (.el, .info, etc.)
when installing&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;--disable-silent-rules&lt;&#x2F;code&gt;: enable verbose build output&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Run the &lt;code&gt;configure&lt;&#x2F;code&gt; script with these options to create the &lt;code&gt;Makefile&lt;&#x2F;code&gt; you&#x27;ll
use to build Emacs.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.&#x2F;configure --with-native-compilation \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            --with-json \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            --with-ns \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            --with-xwidgets \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            --without-dbus \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            --without-compress-install \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            --disable-silent-rules&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After this finishes, it&#x27;s time to build Emacs proper.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;build-emacs&quot;&gt;Build emacs&lt;&#x2F;h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: if &lt;code&gt;make&lt;&#x2F;code&gt; fails, take a look at &quot;Troubleshooting&quot; down below. Your best
bet is to run &lt;code&gt;make bootstrap&lt;&#x2F;code&gt; instead.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This creates an Emacs binary at &lt;code&gt;src&#x2F;emacs&lt;&#x2F;code&gt;. You can verify that everything
worked properly by running &lt;code&gt;emacs -Q&lt;&#x2F;code&gt;, launching it with no configuration.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;src&#x2F;emacs -Q&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After you&#x27;ve verified that everything is good to go, the last step is to
assemble &lt;code&gt;Emacs.app&lt;&#x2F;code&gt; proper:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make install&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You&#x27;ll notice that a hefty &lt;code&gt;Emacs.app&lt;&#x2F;code&gt; application now lives in the &lt;code&gt;nextstep&#x2F;&lt;&#x2F;code&gt;
directory. Go ahead and move it into your &lt;code&gt;&#x2F;Applications&#x2F;&lt;&#x2F;code&gt; directory.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mv nextstep&#x2F;Emacs.app &#x2F;Applications&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I also like to include &lt;code&gt;src&lt;&#x2F;code&gt; and &lt;code&gt;lib-src&lt;&#x2F;code&gt; on &lt;code&gt;PATH&lt;&#x2F;code&gt; so I can run Emacs from the
CLI (particularly important for &lt;code&gt;emacsclient&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Syntax for fish shell&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;set PATH $HOME&#x2F;projects&#x2F;emacs&#x2F;src $PATH&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;set PATH $HOME&#x2F;projects&#x2F;emacs&#x2F;lib-src $PATH&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Congratulations, you have officially built Emacs from source!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;next-steps&quot;&gt;Next steps&lt;&#x2F;h3&gt;
&lt;p&gt;With everything running smoothly, you&#x27;re now ready to make your first
contributions to the Emacs codebase. Here are some excellent guides to get
started:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.fosskers.ca&#x2F;en&#x2F;blog&#x2F;contributing-to-emacs&quot;&gt;Contributing to Emacs&lt;&#x2F;a&gt;
by Colin Woodbury&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lars.ingebrigtsen.no&#x2F;2014&#x2F;11&#x2F;13&#x2F;welcome-new-emacs-developers&#x2F;?utm_source=pocket_mylist&quot;&gt;Welcome, New Emacs Developers&lt;&#x2F;a&gt;
by Lars Ingebrigtsen&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Best of luck, new Emacs contributor!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;&#x2F;h2&gt;
&lt;p&gt;If &lt;code&gt;make&lt;&#x2F;code&gt; fails, one of the easiest ways to resolve most problems is to use the
bootstrap script instead:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Clean out any dangling build artifacts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make clean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make bootstrap&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is effectively a &quot;slower and more thorough&quot; build of the application, and
successfully resolved a few issues I ran into when I updated from Emacs 28
to 29.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reading: The Dispossessed</title>
        <published>2022-08-13T00:00:00+00:00</published>
        <updated>2022-08-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-08-13-reading-the-dispossessed/"/>
        <id>https://mgmarlow.com/words/2022-08-13-reading-the-dispossessed/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-08-13-reading-the-dispossessed/">&lt;p&gt;Talking about &lt;em&gt;The Dispossessed&lt;&#x2F;em&gt; by Ursula K. Le Guin.&lt;&#x2F;p&gt;
&lt;p&gt;Before diving into the contents of this book, it&#x27;s interesting to note that
Ursula K. Le Guin was a big fan of Taoism, big enough to publish
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.ursulakleguin.com&#x2F;lao-tzu-the-tao-te-ching&quot;&gt;her own commentary&lt;&#x2F;a&gt; on
the Tao Te Ching. This perspective lends an additional layer of analysis when
discussing &lt;em&gt;The Dispossessed&lt;&#x2F;em&gt;, making it difficult to discuss the book without
looking for influences from one to the other.&lt;&#x2F;p&gt;
&lt;p&gt;In particular, where Taoism was a rejection of traditionalist and hierarchical
Confucianism, so too Odonian ethics a rejection of Urras capitalism. These
ethics embody principles that on a whole resemble those of Taoism, particularly
with their emphasis on work being a reward in and of itself, a rejection of
&quot;egoism&quot; and accomplishments aimed at elevating the image of self, and the
&quot;acting to one&#x27;s own nature&quot; that is consistent with the lack of laws and
governmental structure on Anarres.&lt;&#x2F;p&gt;
&lt;p&gt;In a way, Shevek can be portrayed as a Taoist Sage. Shevek keeps his theory
protected not for the pursuit of aggrandizement, but to release it
intergalactically such that all can benefit. &quot;Hence the sage is able [...] to
accomplish his great achievements. It is through his not making himself great
that he can accomplish them&quot; (p. 89 &lt;em&gt;Tao Te Ching&lt;&#x2F;em&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;The relation to Taoism aside, what struck me most about this novel was its
achievement not as political commentary, but as an expression of empathy and
unity. Shevek&#x27;s struggle with shared suffering and problems common to humans
forming communities of governance make for an excellent mirror for
self-reflection. None of the communities are without issue; the narrative lens
does not focus on perfection of institutions but rather the exploration of
learning from shared experience. An &quot;ambiguous utopia&quot; indeed.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Emacs from scratch</title>
        <published>2022-05-08T00:00:00+00:00</published>
        <updated>2024-04-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-05-02-learning-emacs/"/>
        <id>https://mgmarlow.com/words/2022-05-02-learning-emacs/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-05-02-learning-emacs/">&lt;blockquote&gt;
&lt;p&gt;Update: I created &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&quot;&gt;Start Emacs&lt;&#x2F;a&gt; as an
easier way to get up and running with Emacs. It follows many of the same
principles in this guide and throws in some extras.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;start-emacs&quot;&gt;Check it out here&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Four months after writing this article and I&#x27;m still using Emacs full-time with
my own, custom configuration. I continue to find great pleasure in extending my
setup with small snippets I stumble upon from Emacs hackers far wiser than
myself. Those tweaks aside, my
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;dotemacs&quot;&gt;current configuration&lt;&#x2F;a&gt; is going strong.&lt;&#x2F;p&gt;
&lt;p&gt;I maintain the opinion that creating your own Emacs configuration is not nearly
as daunting as is often presented. With a few tweaks to the base defaults you
can be up-and-running with an editor that looks and feels far closer to modern
alternatives. A few packages later and you&#x27;re well on your way to Emacs nirvana.&lt;&#x2F;p&gt;
&lt;p&gt;For those of you who have decided to build your own configuration from scratch:
enjoy the process! The greatest thing about Emacs is the satisfaction of
building a tool to suit your preferences. I hope this article helps you on that
journey.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Original article&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Like any self-respecting programmer, I often find myself struggling with text
editor envy. This time, like the three previous times, the source of that envy
is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;&quot;&gt;Emacs&lt;&#x2F;a&gt;. All it takes is a stray link to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacsrocks.com&#x2F;&quot;&gt;emacsrocks&lt;&#x2F;a&gt; and I&#x27;m back in that vicious cycle.&lt;&#x2F;p&gt;
&lt;p&gt;Unlike those previous flings, however, I have managed to stick to Emacs with a
new level of tenacity. I have even found myself &lt;em&gt;enjoying&lt;&#x2F;em&gt; it.&lt;&#x2F;p&gt;
&lt;p&gt;What has changed is my fundamental approach to learning Emacs. Rather than
reaching for a prebuilt distribution, like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;doomemacs&#x2F;doomemacs&quot;&gt;doomemacs&lt;&#x2F;a&gt; or
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.spacemacs.org&#x2F;&quot;&gt;spacemacs&lt;&#x2F;a&gt;, I decided to build my own configuration
from scratch.&lt;&#x2F;p&gt;
&lt;p&gt;This effort, previously believed insurmountable, was easy and educational. With
just a few packages, I managed to match the functionality of my primary text
editor. Most importantly, I found myself reaching for Emacs&#x27;s built-in help
system instead of scouring the web, reinforcing my familiarity with the tool and
accelerating my proficiency.&lt;&#x2F;p&gt;
&lt;p&gt;Prebuilt distributions are an awesome demonstration of what is possible with
Emacs, but I don&#x27;t think they&#x27;re a good introduction to the editor. Instead, I
think the best way to get started with Emacs is with a minimum-viable
configuration. Just enough stuff to bring Emacs up to par with a modern editor
and nothing else.&lt;&#x2F;p&gt;
&lt;p&gt;Follow along with the rest of this article and you&#x27;ll have your own, very
capable Emacs ready for experimentation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;first-steps&quot;&gt;First steps&lt;&#x2F;h2&gt;
&lt;p&gt;After &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;&quot;&gt;downloading Emacs&lt;&#x2F;a&gt;, I recommend
running through the built-in tutorial
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;tour&#x2F;&quot;&gt;also available on the web&lt;&#x2F;a&gt;). Some
basic knowledge of Emacs keybindings will help make the rest of this guide
easier to follow.&lt;&#x2F;p&gt;
&lt;p&gt;If looking at the default Emacs theme is too much to bear, swap to one of the
dark-mode themes:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M-x load-theme RET deeper-blue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Remember: &lt;code&gt;C-&amp;lt;chr&amp;gt;&lt;&#x2F;code&gt; means hold the Control key while typing the character
&lt;code&gt;&amp;lt;chr&amp;gt;&lt;&#x2F;code&gt;. &lt;code&gt;M-&amp;lt;chr&amp;gt;&lt;&#x2F;code&gt; means hold the Meta key (Alt on Windows) and pressing
&lt;code&gt;&amp;lt;chr&amp;gt;&lt;&#x2F;code&gt;. (Emacs tutorial)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;rebind-caps-lock-to-control&quot;&gt;Rebind caps lock to control&lt;&#x2F;h2&gt;
&lt;p&gt;One thing you&#x27;ll quickly notice from the tutorial, if you weren&#x27;t already aware
of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Emacs#Emacs_pinky&quot;&gt;Emacs pinky&lt;&#x2F;a&gt;, is that
pressing Control all of the time can get pretty taxing on your wrist. RSI is no
joke! Take care of those wrists.&lt;&#x2F;p&gt;
&lt;p&gt;Rebind your Caps Lock key to Control and never look back.&lt;&#x2F;p&gt;
&lt;p&gt;Instructions:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;15435253&#x2F;how-to-remap-the-caps-lock-key-to-control-in-os-x-10-8&quot;&gt;MacOS&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;joshschmelzle&#x2F;5e88dabc71014d7427ff01bca3fed33d&quot;&gt;Windows 10&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;start-your-configuration&quot;&gt;Start your configuration&lt;&#x2F;h2&gt;
&lt;p&gt;All of your Emacs files will go into &lt;code&gt;.emacs.d&lt;&#x2F;code&gt;, so go ahead and create a new
directory:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Cool stuff goes in here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mkdir ~&#x2F;.emacs.d&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Back in Emacs, make a new file with &lt;code&gt;M-x find-file&lt;&#x2F;code&gt;, or the following
keybinding:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;C-x C-f ~&#x2F;.emacs.d&#x2F;init.el&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will create a new Emacs Lisp file, &lt;code&gt;init.el&lt;&#x2F;code&gt;, in your Emacs configuration
directory. Emacs will automatically load this file on startup.&lt;&#x2F;p&gt;
&lt;p&gt;From here, you can go ahead and start making some changes to your editor theme:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Load a dark mode theme&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;load-theme&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;deeper-blue t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Although many guides will recommend removing the ugly menu&#x2F;tool bars at the top
of your editor, I think it&#x27;s a good idea to keep them around until you&#x27;re more
comfortable with Emacs keybindings. The very sight of them may offend your
sensibilities, but at least they can help you when you forget basic editor
functions.&lt;&#x2F;p&gt;
&lt;p&gt;That said, if you want to remove them you can do so with the following code:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Disable menu bar, scroll bar, and tool bar&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;menu-bar-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;scroll-bar-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;tool-bar-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After saving, you can apply the changes from your configuration file by closing
Emacs and re-opening it. Alternatively, you can apply the changes immediately in
Emacs by using the command &lt;code&gt;M-x eval-buffer&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;sensible-defaults&quot;&gt;Sensible defaults&lt;&#x2F;h2&gt;
&lt;p&gt;Once Emacs is themed to your preference, I&#x27;d recommend tweaking some of the
built-in settings with &quot;better&quot; defaults. Emacs has been around for a long time
(as you can probably tell by the default theme) and these settings put it more
in line with modern editors.&lt;&#x2F;p&gt;
&lt;p&gt;My recommendations are either sourced from other configuration files I found in
the wild (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.sr.ht&#x2F;~technomancy&#x2F;better-defaults&quot;&gt;better-defaults&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SystemCrafters&#x2F;crafted-emacs&quot;&gt;crafted-emacs&lt;&#x2F;a&gt; are two great
examples) or from tweaking the performance of some CPU-intensive packages like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;emacs-lsp.github.io&#x2F;lsp-mode&#x2F;page&#x2F;performance&#x2F;&quot;&gt;lsp-mode&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;First up are some performance threshold tweaks that will help Emacs run faster
on a modern machine. Recall that you can look up the help documentation of any
of these variables by invoking &lt;code&gt;C-h v&lt;&#x2F;code&gt;, or &lt;code&gt;M-x describe-variable&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq gc-cons-threshold &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;100000000&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; ; 100 mb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq read-process-output-max &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1024 1024&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt; ; 1mb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The remaining recommendations are more personal in nature. I&#x27;d urge you to read
about each of these options and decide for yourself whether you&#x27;d like to
include them. In particular, you may find yourself enjoying the customization UI
that ships with Emacs
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Easy-Customization.html&quot;&gt;&lt;code&gt;M-x customize&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;)
more than me.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Auto-refresh buffers when files on disk change.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;global-auto-revert-mode&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Ensure unique names when matching files exist in the buffer.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; e.g. This helps when you have multiple copies of &amp;quot;main.rs&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; open in different projects. It will add a &amp;quot;myproj&#x2F;main.rs&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; prefix when it detects matching names.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;uniquify&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq uniquify-buffer-name-style &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;forward&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Place backups in a separate folder.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq backup-directory-alist &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;`((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;~&#x2F;.saves&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq auto-save-file-name-transforms &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;`((&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;.*&amp;quot; &amp;quot;~&#x2F;.saves&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; I store automatic customization options in a gitignored file,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; but this is definitely a personal preference.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;setq custom-file &lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;locate-user-emacs-file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;custom.el&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;when&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;file-exists-p&lt;&#x2F;span&gt;&lt;span&gt; custom-file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt; custom-file&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;get-ready-for-packages&quot;&gt;Get ready for packages&lt;&#x2F;h2&gt;
&lt;p&gt;While Emacs ships with a ton of useful features, we&#x27;re all here for the
packages. This is the moment where you can really start turning your Emacs from
text editor status into full-blown operating system.&lt;&#x2F;p&gt;
&lt;p&gt;By default, Emacs comes with a curated package registry in the form of
&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;elpa.gnu.org&#x2F;&quot;&gt;GNU Elpa&lt;&#x2F;a&gt;. You&#x27;ll want to extend this default list of
packages with those from a larger, also curated list called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;melpa.org&#x2F;&quot;&gt;MELPA&lt;&#x2F;a&gt;. With these two lists (and Emacs&#x27;s built-in
packages), you can find just about everything you&#x27;ll ever need.&lt;&#x2F;p&gt;
&lt;p&gt;Add the following lines to your &lt;code&gt;~&#x2F;.emacs.d&#x2F;init.el&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; You&amp;#39;ll be installing your packages with the&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; built-in package.el script&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;package&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Add MELPA to your list of package archives&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;add-to-list&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;package-archives&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;	         &amp;#39;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;melpa&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;https:&#x2F;&#x2F;melpa.org&#x2F;packages&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;package-initialize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; Go ahead and refresh your package list to&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;;; make sure everything is up-to-date&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;unless&lt;&#x2F;span&gt;&lt;span&gt; package-archive-contents&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;package-refresh-contents&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;install-your-first-package&quot;&gt;Install your first package&lt;&#x2F;h2&gt;
&lt;p&gt;With MELPA ready to go, it&#x27;s time to install your first package. Add the
following to your &lt;code&gt;~&#x2F;.emacs.d&#x2F;init.el&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;unless&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;package-installed-p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;use-package&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;package-install&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;use-package&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jwiegley&#x2F;use-package&quot;&gt;&lt;code&gt;use-package&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a very deliberate
first install, as you&#x27;ll use it instead of &lt;code&gt;package-install&lt;&#x2F;code&gt; to install all of
your other packages.&lt;&#x2F;p&gt;
&lt;p&gt;Why not just keep installing packages with &lt;code&gt;package-install&lt;&#x2F;code&gt;? The reason has to
do with the customization that you&#x27;ll add to the packages after you install
them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;use-package&lt;&#x2F;code&gt; bundles your keybindings, custom variables, package hooks, and
dependencies together in a convenient and well-organized API. While you could
manage all of these details manually, it&#x27;s very convenient to use &lt;code&gt;use-package&lt;&#x2F;code&gt;
instead. It makes it easier to install new packages while keeping your &lt;code&gt;init.el&lt;&#x2F;code&gt;
organized and clean.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-importance-of-text-completion&quot;&gt;The importance of text completion&lt;&#x2F;h2&gt;
&lt;p&gt;I know you&#x27;re excited to install packages, but forgive me for a brief aside. By
far the most important thing to configure in Emacs is your
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Completion.html&quot;&gt;completion framework&lt;&#x2F;a&gt;.
Doing so will not only make Emacs more familiar when coming from existing
editors, it will drive your ability to teach yourself Emacs &lt;em&gt;from within Emacs&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;But before you grab a text completion package off the internet, I think it&#x27;s
helpful to take a look at Emacs&#x27;s built-in capabilities. Doing so will help you
make some informed decisions about what features you&#x27;d like a text completion
framework to fulfill.&lt;&#x2F;p&gt;
&lt;p&gt;That said, the completion framework ecosystem within Emacs is nuanced and
idiosyncratic, but I&#x27;ll try my best to break it down.&lt;&#x2F;p&gt;
&lt;p&gt;When you attempt any operation that prompts you for text in Emacs, you&#x27;ll be
presented with a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Minibuffer.html&quot;&gt;minibuffer&lt;&#x2F;a&gt;
asking for you to fill in that information. Oftentimes that information will be
the path to a file or an Emacs Lisp function. In either of these cases it&#x27;s
vastly preferable to have Emacs fill in your text so you don&#x27;t have to type it
all in yourself (hence, completion).&lt;&#x2F;p&gt;
&lt;p&gt;With the default Emacs completion framework, you allow Emacs to fill in text for
you by mashing the &lt;code&gt;TAB&lt;&#x2F;code&gt; key endlessly. Go ahead and try it: &lt;code&gt;C-x C-f&lt;&#x2F;code&gt;, begin
typing a path, then have Emacs autocomplete it with &lt;code&gt;TAB TAB&lt;&#x2F;code&gt;. Even worse is
looking up an Emacs command with &lt;code&gt;M-x&lt;&#x2F;code&gt;. Try &lt;code&gt;M-x find TAB TAB&lt;&#x2F;code&gt;. Functional,
sure. But also a surefire path to RSI.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily there are a few built-in alternatives, a subject to which Mickey
Peterson
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.masteringemacs.org&#x2F;article&#x2F;understanding-minibuffer-completion&quot;&gt;dedicates an entire blog post&lt;&#x2F;a&gt;.
I&#x27;ll mention one here that is a personal favorite, &lt;code&gt;fido-vertical-mode&lt;&#x2F;code&gt; which
uses Emacs
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Icomplete.html&quot;&gt;icomplete&lt;&#x2F;a&gt;
under the hood.&lt;&#x2F;p&gt;
&lt;p&gt;Give it a try and you&#x27;ll immediately notice the difference:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M-x fido-vertical-mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Try to lookup an Emacs command like &lt;code&gt;M-x find-file&lt;&#x2F;code&gt;. It even includes the
keybindings in the margin!&lt;&#x2F;p&gt;
&lt;p&gt;This exact benefit is the reason I dedicated an entire section of this guide to
your chosen completion framework. It makes it infinitely easier to lookup Emacs
commands when searching for documentation or trying to remember the keybindings.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;vertico-and-marginalia&quot;&gt;Vertico and Marginalia&lt;&#x2F;h2&gt;
&lt;p&gt;Now, if &lt;code&gt;fido-vertical-mode&lt;&#x2F;code&gt; works for you, great! No need to install a separate
completion framework. However I find that an alternative,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;minad&#x2F;vertico&quot;&gt;Vertico&lt;&#x2F;a&gt;, offers both better performance and
greater features.&lt;&#x2F;p&gt;
&lt;p&gt;Vertico is built by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;minad&quot;&gt;Daniel Mendler&lt;&#x2F;a&gt; and integrates
nicely into other tools by the same author, as well as Emacs built-ins. One I&#x27;ll
recommend alongside Vertico is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;minad&#x2F;marginalia&quot;&gt;Marginalia&lt;&#x2F;a&gt;, a package that adds more
annotations to your completions. Similar to the keybindings in the earlier
example with &lt;code&gt;M-x find-file&lt;&#x2F;code&gt;, Marginalia displays the beginning of the entire
docstring.&lt;&#x2F;p&gt;
&lt;p&gt;Install the packages with &lt;code&gt;use-package&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; vertico&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;vertico-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; marginalia&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;after vertico&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;ensure t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span&gt;marginalia-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, I like to throw &lt;code&gt;savehist&lt;&#x2F;code&gt; in there as well so my recent completions
appear at the top of the minibuffer. This one omits &lt;code&gt;:ensure t&lt;&#x2F;code&gt; since it ships
with Emacs.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;emacs-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;use-package&lt;&#x2F;span&gt;&lt;span&gt; savehist&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  :&lt;&#x2F;span&gt;&lt;span&gt;init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;savehist-mode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;finding-help&quot;&gt;Finding Help&lt;&#x2F;h2&gt;
&lt;p&gt;Your choice of completion framework is a huge benefit when learning Emacs, as
the entire library of Emacs commands is available at your fingertips with &lt;code&gt;M-x&lt;&#x2F;code&gt;.
As soon as I discovered Vertico and Marginalia I threw out my Emacs cheatsheet.
When all of Emacs&#x27;s documentation is so easily accessible, there&#x27;s really no
reason to keep one around for reference.&lt;&#x2F;p&gt;
&lt;p&gt;When it comes to reaching out to Emacs for help, remember the following:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;You can execute any Emacs command with &lt;code&gt;M-x &amp;lt;command&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Keybindings are available in the minibuffer thanks to Marginalia, but you can
also discover them with &lt;code&gt;M-x where-is RET &amp;lt;command&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;You can view the documentation of any command you know how to press with
&lt;code&gt;C-h c&lt;&#x2F;code&gt;. Try &lt;code&gt;C-h c RET C-x C-f&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;C-h ?&lt;&#x2F;code&gt; is an all-encompassing help menu for you to get your bearings.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;As long as you&#x27;re familiar with basic Emacs terminology (e.g. buffers, windows,
and frames) every Emacs command is available to you with a quick search.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;project-management-with-project-el&quot;&gt;Project management with project.el&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Edit&lt;&#x2F;strong&gt;: This used to be a section about
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bbatsov&#x2F;projectile&quot;&gt;Projectile&lt;&#x2F;a&gt;, a package that provides
project navigation utilities to Emacs. Since writing this article, I&#x27;ve
actually found the default Emacs package &lt;code&gt;project.el&lt;&#x2F;code&gt; to be a completely
viable alternative, no extra package needed. YMMV, so definitely check out
Projectile if you need more than is provided by &lt;code&gt;project.el&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Navigating files with &lt;code&gt;C-x C-f&lt;&#x2F;code&gt; is incredibly cumbersome, particularly for large
projects. Far better is the Emacs Project framework,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Projects.html&quot;&gt;project.el&lt;&#x2F;a&gt;.
As long as you have a project that is initialized with &lt;code&gt;git&lt;&#x2F;code&gt;, &lt;code&gt;project.el&lt;&#x2F;code&gt; lets
you search files with minibuffer completion, grep queries with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Xref.html&quot;&gt;xref&lt;&#x2F;a&gt;, and
manage directories with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;emacs&#x2F;manual&#x2F;html_node&#x2F;emacs&#x2F;Dired.html&quot;&gt;dired&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;First, open up a project by browsing to a version-controlled file with
&lt;code&gt;C-x C-f&lt;&#x2F;code&gt;. You&#x27;ll now be able to use the &lt;code&gt;project.el&lt;&#x2F;code&gt; commands to navigate this
project:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-x p f&lt;&#x2F;code&gt;: Visit a file in the current project.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;C-x p g&lt;&#x2F;code&gt;: Grep a query across all files in the current project.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;C-x p d&lt;&#x2F;code&gt;: Open dired in the chosen directory.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As soon as you&#x27;ve opened a project one time, it&#x27;ll be available in the project
cache. You can browse any previously-visited projects on your system with
&lt;code&gt;C-x p p&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;&#x2F;h2&gt;
&lt;p&gt;With &lt;code&gt;use-package&lt;&#x2F;code&gt;, &lt;code&gt;Vertico&lt;&#x2F;code&gt;, and the basics of &lt;code&gt;project.el&lt;&#x2F;code&gt; under your
fingertips, you&#x27;re ready to explore the rest that Emacs has to offer. Here are a
few branching points that I would suggest based on your goals.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If you want your Emacs experience to have IDE-like code-completion, look into
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;company-mode.github.io&#x2F;&quot;&gt;company-mode&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;joaotavora&#x2F;eglot&quot;&gt;eglot&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;If you want to be amazed by one of the best git interfaces in the world of
text editors, dive into &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;magit.vc&#x2F;&quot;&gt;Magit&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;If you&#x27;re interested in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;orgmode.org&#x2F;&quot;&gt;org-mode&lt;&#x2F;a&gt; and follow the
Zettelkasten hype train, read all about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.orgroam.com&#x2F;&quot;&gt;org-roam&lt;&#x2F;a&gt;
or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;protesilaos.com&#x2F;emacs&#x2F;denote&quot;&gt;denote&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Outside of new packages, I also recommend reading some open source Emacs
configurations that are out in the wild. Here are a few projects I&#x27;ve taken
inspiration from:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Technomancy&#x27;s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git.sr.ht&#x2F;~technomancy&#x2F;better-defaults&quot;&gt;Better defaults&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;System Crafter&#x27;s
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SystemCrafters&#x2F;crafted-emacs&#x2F;&quot;&gt;Crafted Emacs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Welcome to your new Emacs journey!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Clean git</title>
        <published>2022-02-12T00:00:00+00:00</published>
        <updated>2022-02-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-02-12-clean-git/"/>
        <id>https://mgmarlow.com/words/2022-02-12-clean-git/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-02-12-clean-git/">&lt;p&gt;Well-organized commits are a treat. They are your guide to the theory of
developers come and gone, a ledger filled with important decisions. Knowing how
to structure commits into a readable, retrievable, and reviewable history is one
of the keys to a maintainable codebase.&lt;&#x2F;p&gt;
&lt;p&gt;Compare a change request (CR) with the following commits:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8e74f78 Add new button feature WIP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;47e9c34 Fix linter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b409804 Finish button feature&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0643984 Cleanup&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To one with more detail, yet fewer commits:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8e74f78 feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;47e9c34 fix: Button tooltip and disabled state clash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;When a button has a tooltip and is disabled, ensure that the tooltip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;theme matches that of the disabled state.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Before even looking at the code, which CR would you prefer reviewing?&lt;&#x2F;p&gt;
&lt;p&gt;This isn&#x27;t a lesson on how to write good commits, because
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cbea.ms&#x2F;git-commit&#x2F;&quot;&gt;cbeams has that down to a science&lt;&#x2F;a&gt;. Instead, this
post covers the tools and workflows that make writing good commits easier.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;responding-to-cr-comments&quot;&gt;Responding to CR comments&lt;&#x2F;h2&gt;
&lt;p&gt;There are few experiences more frustrating than using &lt;code&gt;git blame&lt;&#x2F;code&gt; on a line of
code, praying some amount of insight can save your lack of understanding, only
to be met with &quot;PR changes&quot;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; add_ten&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;v&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  # git blame: You, 3 years ago. &amp;quot;PR changes&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; something_that_has_nothing_to_do_with_anything&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 15&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    v &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Please do not create, let alone merge, commits titled &quot;PR changes&quot;. Instead,&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;docs&#x2F;git-commit#Documentation&#x2F;git-commit.txt---fixupamendrewordltcommitgt&quot;&gt;fixup commits&lt;&#x2F;a&gt;
during the CR review to inform your reviewer of changes&lt;&#x2F;li&gt;
&lt;li&gt;use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;docs&#x2F;git-rebase#Documentation&#x2F;git-rebase.txt---interactive&quot;&gt;interactive rebasing&lt;&#x2F;a&gt;
to squash your fixups into the commits they&#x27;re fixing&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;fixing-commits&quot;&gt;Fixing commits&lt;&#x2F;h3&gt;
&lt;p&gt;The temptation to create the &quot;PR changes&quot; commit comes from a good place. You
don&#x27;t want to &lt;code&gt;force push&lt;&#x2F;code&gt; your branch, because it makes it hard to re-review
changes and breaks local collaborators. Moreover, those commits aren&#x27;t really
doing anything new—they&#x27;re just fixing what you wrote previously.&lt;&#x2F;p&gt;
&lt;p&gt;Fixups offer a better way. Say you have the following commits in CR:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git log --oneline&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b409804 feat: Disable form on validation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8e74f78 feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;22e8539 Initial commit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Your CR reviewer points out a bug introduced by your button component, so you
make the changes and stage them:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git status -s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M Button.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Your new changes are necessary to fix your prior commit, &quot;feat: Add disabled
state to button&quot;, and don&#x27;t belong as a separate thought. Instead of creating a
new commit manually, use &lt;code&gt;git commit --fixup &amp;lt;SHA&amp;gt;&lt;&#x2F;code&gt;, indicating that your
changes fix a prior commit:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit --fixup 8e74f78&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[main e69dff9] fixup! feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; 1 file changed, 1 insertion(+), 1 deletion(-)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git log --oneline&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;e69dff9 fixup! feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b409804 feat: Disable form on validation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8e74f78 feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;22e8539 Initial commit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With &lt;code&gt;--fixup&lt;&#x2F;code&gt;, git automatically creates a new commit for you with a special
&lt;code&gt;fixup!&lt;&#x2F;code&gt; prefix. The rest of the commit&#x27;s title is identical to the one it&#x27;s
fixing.&lt;&#x2F;p&gt;
&lt;p&gt;Now you&#x27;re ready to &lt;code&gt;git push&lt;&#x2F;code&gt; that branch and await your CR approval.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;squashing-fixups-for-clean-history&quot;&gt;Squashing fixups for clean history&lt;&#x2F;h3&gt;
&lt;p&gt;Fixups are great for CRs, but you don&#x27;t want them to clutter your project&#x27;s
history. After CR approval, you need to perform an extra step before you can
merge your CR into the main branch.&lt;&#x2F;p&gt;
&lt;p&gt;Use interactive rebase with the &lt;code&gt;--autosquash&lt;&#x2F;code&gt; option to automatically squash
your fixup commits into their respective targets:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git log --oneline&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;e69dff9 fixup! feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b409804 feat: Disable form on validation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8e74f78 feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;22e8539 Initial commit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git rebase --interactive 22e8539 --autosquash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Within the interactive rebase window, your &lt;code&gt;fixup!&lt;&#x2F;code&gt; commits are automatically
repositioned with the &lt;code&gt;fixup&lt;&#x2F;code&gt; action (instead of &lt;code&gt;pick&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pick 8e74f78 feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fixup e69dff9 fixup! feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pick b409804 feat: Disable form on validation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once you save this window your fixup commits are squashed into their target
commits and your history is left clean:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git log --oneline&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;b409804 feat: Disable form on validation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;8e74f78 feat: Add disabled state to button&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;22e8539 Initial commit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now you can &lt;code&gt;git push --force&lt;&#x2F;code&gt; this branch and merge it into main.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unwinding-over-eager-commits&quot;&gt;Unwinding over-eager commits&lt;&#x2F;h2&gt;
&lt;p&gt;Sometimes you&#x27;re in a rush to get your changes up on the server, so you throw
all of your changes into one big &quot;WIP&quot; commit:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit -am &amp;quot;WIP&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s fine for local development, but as soon as you expect someone to review
your CR you had better break apart your history into something meaningful.&lt;&#x2F;p&gt;
&lt;p&gt;A common strategy is to reset your changes and commit the files one-by-one:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git reset HEAD~1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git status -s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M Button.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;M Form.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;?? helper.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git add Button.tsx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit -m &amp;quot;feat: Add disabled state ...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This strategy works great when every change to a file represents the same
thought. But what about when you make two very different changes to the same
file?&lt;&#x2F;p&gt;
&lt;p&gt;As a contrived example, say you have two new additions to a Rails controller:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git diff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -1,3 +1,9 @@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; class PostsController &amp;lt; ApplicationController&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  def index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+    @posts = Post.all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  def new&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+    @post = Post.new&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Adding this file in one go makes it harder to describe the commit since you&#x27;re
changing two different and unrelated pieces of code.&lt;&#x2F;p&gt;
&lt;p&gt;Rather than staging the entire file, you want to stage each method separately.
To do this, pass in the &lt;code&gt;--patch&lt;&#x2F;code&gt; option when adding the file. This enters
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;docs&#x2F;git-add#Documentation&#x2F;git-add.txt---patch&quot;&gt;interactive patch mode&lt;&#x2F;a&gt;,
which allows you to add your changes as separate units. Each unit is referred to
as a hunk.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git add --patch&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -1,3 +1,9 @@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; class PostsController &amp;lt; ApplicationController&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  def index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+    @posts = Post.all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  def new&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+    @post = Post.new&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(1&#x2F;1) Stage this hunk [y,n,q,a,d,s,e,?]?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In interactive mode, you&#x27;re brought to a screen that looks very similar to a
normal &lt;code&gt;git diff&lt;&#x2F;code&gt;, with the addition of a &quot;Stage this hunk&quot; question at the
bottom.&lt;&#x2F;p&gt;
&lt;p&gt;You don&#x27;t want to stage this hunk as-is. Instead, you want to split it apart
(s):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(1&#x2F;1) Stage this hunk [y,n,q,a,d,s,e,?]? s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Split into 2 hunks.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -1,2 +1,5 @@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; class PostsController &amp;lt; ApplicationController&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  def index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+    @posts = Post.all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(1&#x2F;2) Stage this hunk [y,n,q,a,d,j,J,g,&#x2F;,e,?]?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When you split a hunk, git will automatically divide the changes into two
smaller pieces. After splitting, you can see that the preview window shows (1&#x2F;2)
instead of (1&#x2F;1), indicating that you have two separate hunks ready to stage.&lt;&#x2F;p&gt;
&lt;p&gt;Enter yes (y) on the first hunk and no (n) on the second, staging the changes
made to &quot;index&quot; but not the changes made to &quot;new&quot;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(1&#x2F;1) Stage this hunk [y,n,q,a,d,s,e,?]? s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Split into 2 hunks.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -1,2 +1,5 @@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; class PostsController &amp;lt; ApplicationController&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  def index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+    @posts = Post.all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(1&#x2F;2) Stage this hunk [y,n,q,a,d,j,J,g,&#x2F;,e,?]? y&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -2,2 +5,5 @@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  def new&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+    @post = Post.new&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+  end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(2&#x2F;2) Stage this hunk [y,n,q,a,d,K,g,&#x2F;,e,?]? n&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Take a look at your git status. You&#x27;ll see that your original file has both
unstaged and staged changes, since you accepted the &quot;index&quot; hunk and declined
the &quot;new&quot; hunk:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Changes to be committed:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;modified: app&#x2F;controllers&#x2F;posts_controller.rb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Changes not staged for commit:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;modified: app&#x2F;controllers&#x2F;posts_controller.rb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now you can create two commits, one for each method:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit -m &amp;quot;feat: Add index&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git add app&#x2F;controllers&#x2F;posts_controller.rb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git commit -m &amp;quot;feat: Add new&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;review&quot;&gt;Review&lt;&#x2F;h2&gt;
&lt;p&gt;Commits are important for maintaining the health of your codebase and the sanity
of your CR reviewers. Remember these key tools:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git commit --fixup &amp;lt;SHA&amp;gt;&lt;&#x2F;code&gt;: apply your staged changes as fixes to an existing
commit with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;docs&#x2F;git-commit#Documentation&#x2F;git-commit.txt---fixupamendrewordltcommitgt&quot;&gt;fixup commits&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;git commit --interactive &amp;lt;SHA&amp;gt; --autosquash&lt;&#x2F;code&gt;: automatically squash fixup
commits so they don&#x27;t clutter your history with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;docs&#x2F;git-rebase#Documentation&#x2F;git-rebase.txt---interactive&quot;&gt;interactive rebasing&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;git add --patch&lt;&#x2F;code&gt;: stage changes with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;docs&#x2F;git-add#Documentation&#x2F;git-add.txt---patch&quot;&gt;interactive patch mode&lt;&#x2F;a&gt;
to split up changes that are made in the same file&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Introducing ReScript</title>
        <published>2022-01-10T00:00:00+00:00</published>
        <updated>2022-08-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-01-10-introducing-rescript/"/>
        <id>https://mgmarlow.com/words/2022-01-10-introducing-rescript/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-01-10-introducing-rescript/">&lt;p&gt;A few weeks ago I revisited &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;&quot;&gt;ReScript&lt;&#x2F;a&gt; and
experimented with the ecosystem during Advent of Code. I was pleased to discover
that the language is in a much better place than four years ago.&lt;&#x2F;p&gt;
&lt;p&gt;Although I don&#x27;t think I&#x27;ll be writing ReScript in production any time soon, I
put together a presentation today to introduce it to my colleagues. Below is the
transcript of that presentation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-is-it&quot;&gt;What is it?&lt;&#x2F;h2&gt;
&lt;p&gt;A strongly-typed language that compiles to JavaScript.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Powered by OCaml&lt;&#x2F;li&gt;
&lt;li&gt;Functional feel&lt;&#x2F;li&gt;
&lt;li&gt;More of a TypeScript competitor than alternatives like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elm-lang.org&#x2F;&quot;&gt;Elm&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.purescript.org&#x2F;&quot;&gt;PureScript&lt;&#x2F;a&gt;, or
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;clojurescript.org&#x2F;&quot;&gt;ClojureScript&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Why use it instead of TypeScript?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Only cover a small, curated subset of JavaScript&lt;&#x2F;li&gt;
&lt;li&gt;Types are sound (will not lie to you)&lt;&#x2F;li&gt;
&lt;li&gt;Fast compilation&lt;&#x2F;li&gt;
&lt;li&gt;Minimal type annotations&lt;&#x2F;li&gt;
&lt;li&gt;Gradual adoption strategy&lt;&#x2F;li&gt;
&lt;li&gt;Compiler optimizations&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Emphasizes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;manual&#x2F;latest&#x2F;function#uncurried-function&quot;&gt;Functions&lt;&#x2F;a&gt;
over classes&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;manual&#x2F;latest&#x2F;pattern-matching-destructuring&quot;&gt;Pattern matching&lt;&#x2F;a&gt;
over conditionals or virtual dispatches&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;manual&#x2F;latest&#x2F;variant&quot;&gt;Data modeling&lt;&#x2F;a&gt;
(variants) over string abuse&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;react&#x2F;latest&#x2F;introduction&quot;&gt;First class support for React&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;history&quot;&gt;History&lt;&#x2F;h2&gt;
&lt;p&gt;You may recognize this project under a different name,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;reasonml.github.io&#x2F;&quot;&gt;Reason&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Used by Facebook Messenger&lt;&#x2F;li&gt;
&lt;li&gt;Promised strong types w&#x2F; safe inference derived from OCaml&lt;&#x2F;li&gt;
&lt;li&gt;Reason was just a syntax layer, BuckleScript was doing the real work&lt;&#x2F;li&gt;
&lt;li&gt;BuckleScript was a fork of the OCaml compiler that output JS&lt;&#x2F;li&gt;
&lt;li&gt;BuckleScript and Reason were two separate teams&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Last year, BuckleScript rebranded to &lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;&quot;&gt;ReScript&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Directly integrated Reason into BuckleScript&lt;&#x2F;li&gt;
&lt;li&gt;Combined both teams under the same umbrella&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Reason &amp;amp; BuckleScript are now united as ReScript&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;syntax-comparison&quot;&gt;Syntax comparison&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;ocaml&quot;&gt;OCaml&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; of&lt;&#x2F;span&gt;&lt;span&gt; int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; of&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; rec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; exists_leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; test tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  match&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; with&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; v&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; test v&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;left&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; right&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      exists_leaf test left&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;      ||&lt;&#x2F;span&gt;&lt;span&gt; exists_leaf test right&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; has_even_leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  exists_leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; mod&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;reason&quot;&gt;Reason&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;tree, tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; rec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; exists_leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;test&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  switch&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  | Leaf(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;v&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; test(v)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  | Node(left, right) =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	  exists_leaf(test, left) || exists_leaf(test, right)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; has_even_leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	exists_leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; mod&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;rescript&quot;&gt;ReScript&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt; rec &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Leaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;tree, tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; rec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; existsLeaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;test&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  switch tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  | Leaf(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;v&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; test(v)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  | Node(left, right) =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		existsLeaf(test, left) || existsLeaf(test, right)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; hasEvenLeaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	existsLeaf&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt; mod&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tree&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;type-soundness&quot;&gt;Type soundness&lt;&#x2F;h2&gt;
&lt;p&gt;The ReScript docs state that the ReScript type system is sounder than
TypeScript’s. What does this mean in practice?&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most type systems make a guess at the type of a value and show you a type in
your editor that&#x27;s sometime incorrect. We don&#x27;t do that.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Quick points:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;No &lt;code&gt;undefined&lt;&#x2F;code&gt; or &lt;code&gt;null&lt;&#x2F;code&gt;. There’s interop available, but they don’t exist in
idiomatic ReScript&lt;&#x2F;li&gt;
&lt;li&gt;Types are compiler-only constructs, they won’t affect your runtime performance&lt;&#x2F;li&gt;
&lt;li&gt;Type checking is way faster than &lt;code&gt;tsc&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Almost no need for annotations&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;why-is-typescript-unsound&quot;&gt;Why is TypeScript unsound?&lt;&#x2F;h3&gt;
&lt;p&gt;TypeScript doesn’t prioritize &lt;em&gt;soundness&lt;&#x2F;em&gt;, it prioritizes &lt;em&gt;completeness&lt;&#x2F;em&gt; (which
is totally fine!).&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; nums&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; []&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; nums[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; adder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; What does this output?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;adder&lt;&#x2F;span&gt;&lt;span&gt;(n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.typescriptlang.org&#x2F;play?#code&#x2F;MYewdgzgLgBGCuBbCAuOSBGBTATgbQF0YBeGQgKFEljBPWTwAYDzLxoYBDAE29zoAUnNAkTYcAGhgYRmXAEoSAPnIwuMANTTWAeh0wA6gAtOsbiCwQYUIwEsrIeFAAOTgPxtIIADZYAdN4gAOZCvLgCYFIATPLy5EA&quot;&gt;&lt;em&gt;playground link&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Meanwhile, ReScript will actually throw a compiler error when indexing an array,
since there may not be an item at that index.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Belt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;&#x2F; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Note&lt;&#x2F;span&gt;&lt;span&gt;: no annotations needed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; nums&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; []&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; nums&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; adder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;adder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Compiler&lt;&#x2F;span&gt;&lt;span&gt; error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; [E] Line&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 7&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; column&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; This&lt;&#x2F;span&gt;&lt;span&gt; has&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; option&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;   Somewhere &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;wanted&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can fix the compiler error by wrapping the array access with a &lt;code&gt;switch&lt;&#x2F;code&gt;
statement, defaulting the value to &lt;code&gt;0&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Belt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; nums&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; []&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; switch nums&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| Some(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;v&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; v&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| None =&amp;gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; adder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;adder&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is just one of many examples of TypeScript’s unsoundness. Here’s another:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Record&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DCEB;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DCEB;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;d4346fda-7480-4e47-a51a-786a431b3272&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span style=&quot;font-style: italic;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; number&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; })&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Runtime type is `string`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;  &#x2F;&#x2F; Type is `number | undefined` ❌&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Expected error here but got none&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span&gt;(a)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.typescriptlang.org&#x2F;play?#code&#x2F;MYewdgzgLgBAhgLhgJQKagE4BMA80MCWYA5gDQz5HEB8MAvDAN4BQMMBWSA5FgCwDMvAGwAzLHAC0Adl4AOAAwTeqXlIlwArAEZJU2ULgCtAI34AmKWa7MAvgG5mzUJFgiw9GAAooSRuywA-EhgAK4AtsaoGDA2AJT0tCxsbAD0KSghYFAEYagwUACeAA55BBAwAAaUJBWsyWkwACrFpeUVoRFRMAA+MJlYqCJEqFgVMIAy5HVszhAgADaoAHRzIMTeixyxDvaODQCiAB4lwFAjMFEYINEAFlF5xiGwxCCwYOCozG6ecFvMQA&quot;&gt;playground link&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The ReScript version outputs a compiler error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;empty&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;set&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;, &amp;quot;id&amp;quot;, &amp;quot;d4346fda-7480-4e47-a51a-786a431b3272&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;option&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;  Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span&gt;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; This&lt;&#x2F;span&gt;&lt;span&gt; has&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;defined as &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Js_dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;&#x2F;  Somewhere &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;wanted&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;    Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;defined as &lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Js_dict&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;option&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;&#x2F;  The &lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;incompatible parts&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;  string vs option&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;variant-and-option&quot;&gt;Variant and Option&lt;&#x2F;h3&gt;
&lt;p&gt;TypeScript tackles variants with string literals:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;tsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Animal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;cat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;dog&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;animal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Animal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  switch&lt;&#x2F;span&gt;&lt;span&gt; (animal)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    case&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;cat&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;I&amp;#39;m a cat&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      break&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    case&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;dog&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      console&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;log&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;I&amp;#39;m a dog&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      break&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Compare this to ReScript’s approach, where variants have dedicated constructors:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; animal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Dog&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Cat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; animal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;  switch animal&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  | Dog &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; Js.log(&amp;quot;I&amp;#39;m a dog&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  | Cat =&amp;gt; Js.log(&amp;quot;I&amp;#39;m a cat&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This gives ReScript variants a lot more flexibility because the constructors can
take arguments:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; account&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Instagram&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Facebook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;string, int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; myAccount&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Facebook&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Josh&amp;quot;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 26&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; friendAccount&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; Instagram&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Jenny&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; acc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; switch acc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| Instagram(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| Facebook(name, age) =&amp;gt; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| None =&amp;gt; ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;process&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;myAccount&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;option&quot;&gt;Option&lt;&#x2F;h3&gt;
&lt;p&gt;An Option is just a special type of Variant.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Recall: &lt;code&gt;undefined&lt;&#x2F;code&gt; and &lt;code&gt;null&lt;&#x2F;code&gt; don’t exist in ReScript&lt;&#x2F;li&gt;
&lt;li&gt;Potentially nonexistent values are still useful&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;&#x2F; Annotation for demonstration only&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; licenseNumber&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; option&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; personHasACar&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Some(5)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Use pattern matching to handle both cases:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;switch licenseNumber {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| None =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Js.log(&amp;quot;The person doesn&amp;#39;t have a car&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| Some(number) =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Js.log(&amp;quot;The person&amp;#39;s license number is &amp;quot; ++ Js.Int.toString(number))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;belt&quot;&gt;Belt&lt;&#x2F;h2&gt;
&lt;p&gt;The ReScript
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;manual&#x2F;latest&#x2F;api&#x2F;belt&quot;&gt;standard library&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Immutable data structures&lt;&#x2F;li&gt;
&lt;li&gt;Safety by default (e.g.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;manual&#x2F;latest&#x2F;api&#x2F;belt#array-access-runtime-safety&quot;&gt;array access runtime safety&lt;&#x2F;a&gt;,
Belt functions never throw exceptions)&lt;&#x2F;li&gt;
&lt;li&gt;Tree-shakable, good performance (citation needed)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; someNumbers&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; greaterThan2UniqueAndSorted&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  someNumbers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Belt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Array&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;keep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt; convert to &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;and&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; set&lt;&#x2F;span&gt;&lt;span&gt; to &lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;make values unique&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Belt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;fromArray&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Belt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;toArray&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; output is already sorted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt;Js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;log2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;result&amp;quot;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; greaterThan2UniqueAndSorted&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When should I use Belt?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You need an API that isn’t available in regular JS&lt;&#x2F;li&gt;
&lt;li&gt;You want no compromises on type safety&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When should I not use Belt?&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You want zero-cost abstractions&lt;&#x2F;li&gt;
&lt;li&gt;You want semantics similar to JS&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Personally, I like to replace the JS standard library with Belt entirely using
this &lt;code&gt;bsconfig&lt;&#x2F;code&gt; setting:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;bsc-flags&amp;quot;: [&amp;quot;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; Belt&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;fibonacci-demo&quot;&gt;Fibonacci demo&lt;&#x2F;h2&gt;
&lt;p&gt;Setup:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;git clone https:&#x2F;&#x2F;github.com&#x2F;rescript-lang&#x2F;rescript-project-template fib-demo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cd fib-demo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;yarn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Problem: &lt;code&gt;f0 = 0, f1 = 1, fn = f{n-1} + f{n-2} for n &amp;gt; 1&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;recursive-process-solution&quot;&gt;Recursive process solution&lt;&#x2F;h3&gt;
&lt;p&gt;ReScript code:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; rec&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  if &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; 2 {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;    fib&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span&gt; - 1) + &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;fib&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span&gt; - 2)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;JavaScript output:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; (n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;lt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;fib&lt;&#x2F;span&gt;&lt;span&gt;((n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fib&lt;&#x2F;span&gt;&lt;span&gt;((n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;iterative-process-still-using-recursion-solution&quot;&gt;Iterative process (still using recursion!) solution&lt;&#x2F;h3&gt;
&lt;p&gt;ReScript code:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;ocaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  let rec &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;iter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; counter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    if &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;counter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt;= 0 {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;      b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;      iter&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt; + &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;b&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;counter&lt;&#x2F;span&gt;&lt;span&gt; - 1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F9E2AF;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  iter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;JavaScript output:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;jsx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; fib&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  var&lt;&#x2F;span&gt;&lt;span&gt; _a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  var&lt;&#x2F;span&gt;&lt;span&gt; _b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  var&lt;&#x2F;span&gt;&lt;span&gt; _counter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;  while&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span&gt; counter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; _counter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; _b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    var&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; _a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; (counter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ===&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;      return&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _counter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (counter&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; b)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    continue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the iterative solution introduces tail call optimization!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;react-demo&quot;&gt;React demo&lt;&#x2F;h2&gt;
&lt;p&gt;Setup:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;npx create-react-app a-todo-list&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then follow the rescript-react
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;react&#x2F;latest&#x2F;installation&quot;&gt;installation instructions&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Full project:
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;rescript-cra&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;rescript-cra&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;should-i-use-rescript&quot;&gt;Should I use ReScript?&lt;&#x2F;h2&gt;
&lt;p&gt;Yes if...&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Your code doesn’t rely too heavily on third-party JS packages&lt;&#x2F;li&gt;
&lt;li&gt;You want total type correctness&lt;&#x2F;li&gt;
&lt;li&gt;You want compiler-defined performance optimizations&lt;&#x2F;li&gt;
&lt;li&gt;You’re using React&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;blog&#x2F;bucklescript-is-rebranding&quot;&gt;BuckleScript &amp;amp; Reason Rebranding&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;formidable.com&#x2F;blog&#x2F;2021&#x2F;reason-2021&#x2F;&quot;&gt;State of Reason in 2021&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;community&#x2F;roadmap&quot;&gt;ReScript official roadmap&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;forum.rescript-lang.org&#x2F;t&#x2F;roles-for-belt-js-and-pervasives-in-rescript&#x2F;1683&#x2F;3&quot;&gt;Roles for Belt, Js, and Pervasives in ReScript&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Reading: Intro to Haiku</title>
        <published>2022-01-08T00:00:00+00:00</published>
        <updated>2022-01-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-01-08-reading-intro-to-haiku/"/>
        <id>https://mgmarlow.com/words/2022-01-08-reading-intro-to-haiku/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-01-08-reading-intro-to-haiku/">&lt;p&gt;Talking about &lt;em&gt;Intro to Haiku&lt;&#x2F;em&gt; by Harold Henderson.&lt;&#x2F;p&gt;
&lt;p&gt;The 17 syllable (5-7-5) structure of Japanese haiku doesn&#x27;t necessarily
translate well into English, so Henderson prefers English haiku in the realm of
12-15. There are a few interesting reasons for this difference:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;There are no articles (the, a, an, etc.) and very few pronouns in Japanese.&lt;&#x2F;li&gt;
&lt;li&gt;Punctuation is replaced by cut-words that have no translatable meaning but
increase syllable count.&lt;&#x2F;li&gt;
&lt;li&gt;Different grammar rules contribute to a very different sentence structure.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That said, even with a shorter syllable count, short-long-short is still the
recommended structure for haiku. The general guidelines for writing haiku are as
follows.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Follow a short-long-short rhythm with fewer than 17 syllables (often 12-15).&lt;&#x2F;li&gt;
&lt;li&gt;Include a single break in meaning that compares two adjacent, though
dissimilar ideas.&lt;&#x2F;li&gt;
&lt;li&gt;Reflect on nature or one of the four seasons.&lt;&#x2F;li&gt;
&lt;li&gt;Try to share an experience of awareness with the reader.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;selected-poems&quot;&gt;Selected poems&lt;&#x2F;h2&gt;
&lt;p&gt;(&lt;em&gt;translations by Harold Henderson&lt;&#x2F;em&gt;)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;basho&quot;&gt;Bashō&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;On a journey, ill,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  and over fields all withered,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    dreams go wandering still.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;A lightning gleam:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  into darkness travels&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    a night heron’s scream.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;issa&quot;&gt;Issa&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;A new year starting, but—&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  it’s still just as it stands here,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    this ramshackle hut!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;shiki&quot;&gt;Shiki&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;The mists come;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  the mountains fade and vanish;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    the tower stands alone.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Night; and once again,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  the while I wait for you,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    cold wind turns into rain.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Icy the moonshine:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  shadow of a tombstone,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    shadow of a pine.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Best of 2021</title>
        <published>2022-01-02T00:00:00+00:00</published>
        <updated>2022-01-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2022-01-02-best-of-2021/"/>
        <id>https://mgmarlow.com/words/2022-01-02-best-of-2021/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2022-01-02-best-of-2021/">&lt;p&gt;Inspired by
&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;blog.fogus.me&#x2F;2021&#x2F;12&#x2F;27&#x2F;the-best-things-and-stuff-of-2021&#x2F;&quot;&gt;Fogus&#x27;s post&lt;&#x2F;a&gt;
of the same name, this is a list of my favorite things of 2021. I&#x27;m focusing on
projects, ideas, and media that I found especially noteworthy throughout the
year (huge bias towards the latter half because my memory is getting old).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;programming-languages-learned&quot;&gt;Programming languages learned&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;&quot;&gt;ReScript&lt;&#x2F;a&gt;: a recent rediscovery, jumping off my
short-lived adventure with Scheme earlier this year. I have been on a
functional programming kick and ReScript has provided just enough functional
delight while maintaining familiar JavaScript underpinnings. I think this tool
has a lot of potential, but it remains a niche project with some hindering
design quirks.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Scheme (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;racket-lang.org&#x2F;&quot;&gt;Racket&lt;&#x2F;a&gt;): more of a dabble and less of a
learn, I tried to work through
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mitpress.mit.edu&#x2F;sites&#x2F;default&#x2F;files&#x2F;sicp&#x2F;index.html&quot;&gt;SICP&lt;&#x2F;a&gt; this year
and put together some
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;sicp-exercises&quot;&gt;neat solutions&lt;&#x2F;a&gt; to the text&#x27;s
exhaustive exercises. Although I only made it through the first chapter,
principles from that reading have invaded my mind and I continue to think
about them even half a year later.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;haxe.org&#x2F;&quot;&gt;Haxe&lt;&#x2F;a&gt;: a fantastic language for making games that will be
familiar to any JavaScript developer. I&#x27;m especially fond of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;haxeflixel.com&#x2F;&quot;&gt;HaxeFlixel&lt;&#x2F;a&gt;, a batteries-included framework for 2D
games.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;some-books-i-read-this-year&quot;&gt;Some books I read this year&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;books&#x2F;franny-and-zooey&#x2F;9780316769495&quot;&gt;Franny &amp;amp; Zooey&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;.
A recommendation from a friend that ended up being one of my favorite books
ever written. The narrative is existential with a spiritual twist, a
coming-of-age novella with well-written characters exploring a complex family
dynamic. This book drew me in with unexpected force, especially given that the
entire novella is little more than four conversations.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;books&#x2F;if-on-a-winter-s-night-a-traveler&#x2F;9780156439619&quot;&gt;If on a Winter&#x27;s Night a Traveler&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;.
I purchased this novel on a lark and was both surprised and amazed by its
premise. A book about reading a book, Calvino blends together several
narrative styles, settings, and plot devices into one cohesive experience. A
pleasant read that offers more insight than appears on the surface.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;books&#x2F;piranesi-9781432886578&#x2F;9781635575637&quot;&gt;Piranesi&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;.
This novel comes highly recommended and I think it deserves the praise. It&#x27;s a
joyful read with just enough barbs to keep you thinking about it after
finishing. In particular, I was drawn to the classical motifs of its setting.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;books&#x2F;domain-driven-design-tackling-complexity-in-the-heart-of-software&#x2F;9780321125217&quot;&gt;Domain-driven Design&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;.
A seminal computer science tome that was an absolute chore to power through.
That said, I don&#x27;t think I&#x27;ve ever cited a text more often in my day-to-day
work. Eric Evans has achieved something remarkable with the thoroughness of
this work, and although it is far from an easy read, it will be remembered for
a long time coming.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;athousandthousandislands.com&#x2F;product&#x2F;kraching&#x2F;&quot;&gt;Kraching&lt;&#x2F;a&gt;&lt;&#x2F;em&gt; (A
Thousand Thousand Islands). Zedeck Siew remains my favorite writer in the
TTRPG space, creating unique and evocative settings with a prose that is more
akin to poetry.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;books&#x2F;intro-to-haiku-an-anthology-of-poems-and-poets-from-basho-to-shiki&#x2F;9780385093767&quot;&gt;Intro to Haiku&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;.
Poems are a great cure for ennui, and haiku in particular is a very
approachable form. Once you step away from the haiku format you were taught as
a kid and learn some fundamentals from the Japanese greats, haiku takes on
another form. A beautiful way of sharing experiences with others.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;On a journey, ill,&lt;&#x2F;p&gt;
&lt;p&gt;and over fields all withered,&lt;&#x2F;p&gt;
&lt;p&gt;dreams go wandering still.&lt;&#x2F;p&gt;
&lt;p&gt;–Basho&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;games-i-played-this-year&quot;&gt;Games I played this year&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Riichi Mahjong: I stumbled into Mahjong in a huge way this year with the help
of my friend, Sam. Once I got a handle on the rules I was wholeheartedly
consumed. The blend of quick decision making and probabilistic reasoning is an
addictive dopamine hit that my brain cannot resist. I even read through Daina
Chiba&#x27;s great introductory
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;dainachiba.github.io&#x2F;RiichiBooks&#x2F;&quot;&gt;strategy book&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Psychonauts 2: the first Psychonauts occupies a special place in my heart, but
I remained thoroughly skeptical of this long-awaited sequel. That said, I was
relieved and impressed to discover that the game is an improvement in every
respect to the original, and provides a uniquely introspective narrative in a
time of wavering psychological health.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Myst, Quern, and Obduction: my partner and I have been on a puzzle-solving
adventure kick. While these games don&#x27;t hold a candle to our favorites, The
Witness or Outer Wilds, playing these games together remotely with Milanote
open in a separate tab is a constant joy. We&#x27;ve learned that ending a session
with an &quot;immediate next steps&quot; list makes it that much easier to resume after
a hiatus.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;games-shipped&quot;&gt;Games shipped&lt;&#x2F;h2&gt;
&lt;p&gt;Shout-out to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.artstation.com&#x2F;nicholaspreheim&quot;&gt;Nick Preheim&lt;&#x2F;a&gt;, who
helped me ship two games this year with his art and collaboration.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mgmarlow.itch.io&#x2F;stonk-market-simulator&quot;&gt;Stonk Market Simulator&lt;&#x2F;a&gt;: a
game jam submission. Inspired by some of the nonsense taking place on
r&#x2F;wallstreetbets earlier in the year.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mgmarlow.itch.io&#x2F;solarflare&quot;&gt;Solarflare&lt;&#x2F;a&gt;: less humor and more game,
another jam submission that tries to mimic classic shmups.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mgmarlow.itch.io&#x2F;noumena&quot;&gt;Noumena&lt;&#x2F;a&gt;: my first go at TTRPG writing, in
the form of a solo journaling game inspired by the bums and poets of the beat
generation. I&#x27;m quite proud of this project and am invigorated to work on more
TTRPG projects in the future.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;albums-listened-to&quot;&gt;Albums listened to&lt;&#x2F;h2&gt;
&lt;p&gt;This year was full of post-punk bangers.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;For the First Time&lt;&#x2F;em&gt; by Black Country, New Road&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Cavalcade&lt;&#x2F;em&gt; by Black Midi&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Entertainment, Death&lt;&#x2F;em&gt; by Spirit of the Beehive&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Bright Green Field&lt;&#x2F;em&gt; by Squid&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;L.W.&lt;&#x2F;em&gt; by King Gizzard &amp;amp; the Wizard Lizard&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;hobbies-old-and-new&quot;&gt;Hobbies old and new&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Warhammer: lockdown inspired the need for creative entertainment that did not
involve a computer. Haven&#x27;t played an actual game yet, but I&#x27;ve already sunk
too many hours into painting minis and reading lore.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;GMing TTRPGs: current system of choice is Pathfinder 2e. I&#x27;ve been a player
for a few years now, but this year I started my first full-length campaign.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Bouldering: unfortunately at the start of this year I
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;theclimbingdoctor.com&#x2F;pulley-injuries-explained-part-1&#x2F;&quot;&gt;ruptured a pulley&lt;&#x2F;a&gt;
in my finger, rendering me unable to climb seriously for most of the year.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;programming-notes&quot;&gt;Programming notes&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;micro-frontends&quot;&gt;Micro frontends&lt;&#x2F;h3&gt;
&lt;p&gt;As JavaScript apps forever grow in size and complexity, I think there remains
room for a microservice-like solution for frontend development. The ideas
proposed in the foundational ThoughtWorks piece remain a good introduction to
the theory, although I think the specific implementation outlined in the article
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;dev.to&#x2F;mgmarlow&#x2F;better-react-micro-frontends-w-nx-5gnm&quot;&gt;is a bit dated&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;four-years-working-with-ruby&quot;&gt;Four years working with Ruby&lt;&#x2F;h3&gt;
&lt;p&gt;Ever since I wanted to program professionally I wanted to work in Ruby, as
&lt;em&gt;Practical OOD&lt;&#x2F;em&gt; and &lt;em&gt;Eloquent Ruby&lt;&#x2F;em&gt; shaped my early programming journey. That&#x27;s
why it&#x27;s particularly surreal that I stumbled into a Ruby gig after years of
working with C# and JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;Ruby has been an absolute pleasure to work with over the last four years, and
I&#x27;ve learned a ton through my interactions with other Rubyists. I think the
community behind the language has something unique, a special friendliness and
freedom of exploration pioneered by open source contributors like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;readme&#x2F;featured&#x2F;why-the-lucky-stiff&quot;&gt;_why the lucky stiff&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Rails is another story. I maintain that Rails is a phenomenal piece of software,
but more often than not its flexibility and rapid capabilities are abused in
ways that create unmaintainable code. Projects are cobbled together quickly for
first-to-market advantage, eschewing any and all upfront design.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a lot of bad Rails code out there in the world, but I&#x27;ll still enjoy
cleaning up my small part of it for the next long while.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sicp-and-recursive-process-vs-recursive-procedure&quot;&gt;SICP and recursive process vs. recursive procedure&lt;&#x2F;h3&gt;
&lt;p&gt;One revelation I experienced while reading SICP was the nuance between recursive
processes and recursive procedures, or, that recursive procedures can be written
iteratively. This is a topic worthy of its own post, but to summarize, check out
this code example that compares
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;replit.com&#x2F;@GrahamMarlow1&#x2F;fib&quot;&gt;two fibonacci implementations&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;embracing-chaotic-notes&quot;&gt;Embracing chaotic notes&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;ve ditched my Zettelkasten and Notion setup in favor of something simpler. The
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zettelkasten.de&#x2F;introduction&#x2F;&quot;&gt;theory behind the Zettelkasten&lt;&#x2F;a&gt; is
fantastic, but I&#x27;ve found it creatively stifling. I worry too much about the
organization of my notes, spending more time architecting pages and browsing
templates than actually writing.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve switched back to writing notes by hand in
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fieldnotesbrand.com&#x2F;&quot;&gt;Field Notes&lt;&#x2F;a&gt; or using dead-simple apps like Apple
Notes. The low barrier-to-entry to get note on page means the important part of
expressing an idea can be done quickly with no distraction.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve learned that I enjoy the process of taking notes more than actually using
the notes as a reference. Only on rare occasions do I actually hunt down my past
notes for a future idea. I write notes for the enjoyment, the process of taking
any idea that flits into my head and establishing it in ink.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Statues of Piranesi</title>
        <published>2021-12-19T00:00:00+00:00</published>
        <updated>2021-12-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2021-12-19-piranesi-statues/"/>
        <id>https://mgmarlow.com/words/2021-12-19-piranesi-statues/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2021-12-19-piranesi-statues/">&lt;p&gt;After finishing
&lt;em&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bookshop.org&#x2F;books&#x2F;piranesi-9781432886578&#x2F;9781635575637&quot;&gt;Piranesi&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;,
my thoughts and attention have been captured by the influences of its setting.
In particular, the marble statues that fill Piranesi’s residence, the House.&lt;&#x2F;p&gt;
&lt;p&gt;The classical influence on Piranesi is just shy of being explicitly stated—not
only is the House is referred to as “the Labyrinth” by several characters, but
its entrance hall is flanked by statues of Minotaurs. Both of these details
suggest a relationship to the heroic tale of Theseus.&lt;&#x2F;p&gt;
&lt;p&gt;The story begins with Poseidon punishing the greedy King of Crete, King Minos,
by enticing his wife with a beautiful bull. The end result of that relationship
is the half-man, half-bull offspring known as the Minotaur.&lt;&#x2F;p&gt;
&lt;p&gt;Ashamed of this creature who had a penchant for devouring human beings, King
Minos hires Daedalus to construct the Labyrinth that serves as the Minotaur’s
prison. What follows is a war with Athens, the ritual sacrificing of fourteen
children every year, and the slaying of the Minotaur thanks Athen’s favorite
tag-team duo Theseus and Ariadne.&lt;&#x2F;p&gt;
&lt;p&gt;With this general connection to Greek myth established, I can now turn to the
statues that continue the trend.&lt;&#x2F;p&gt;
&lt;p&gt;The statue of the Woman with the beehive is perhaps my favorite. After reading
Piranesi’s description, my mind immediately turned towards one of the most
famous similes in the Aeneid,&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Like bees in spring across the blossoming land, Busy beneath the sun, leading
their offspring, Full grown now, from the hive, or loading cells Until they
swell with honey and sweet nectar, Or taking shipments in, or lining up To
guard the fodder from the lazy drones; The teeming work breathes thyme and
fragrant honey. (Aeneid I.430-6)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Virgil describes the industriousness of Carthage, comparing its cohesive
citizens to that of bees in a hive. The simile is especially apt because bees
work in service to their queen as the citizens of Carthage work in service to
Queen Dido.&lt;&#x2F;p&gt;
&lt;p&gt;In Piranesi, the protagonist gives a strikingly similar interpretation of the
statue.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;It occurred to me–it was no more than an idle thought – that both these
Statues might be said to represent Industriousness. The Gardener is old and
bent, and yet he digs faithfully in his garden. The Woman is pursuing her
profession of beekeeping and the Beehive that she carries is full of bees who
are also patiently carrying out their tasks. (Piranesi p. 40)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Since the book does not shy away from its references to Greek and Roman
mythology, I think there’s a strong case for the statue as a direct reference to
Dido and the people of Carthage.&lt;&#x2F;p&gt;
&lt;p&gt;Another statue of interest is Piranesi’s personal favorite, that of the Faun
holding his finger to his lips.&lt;&#x2F;p&gt;
&lt;p&gt;By appearance alone, this could easily be a reference to Pan. More
interestingly, perhaps, is that Pan is described as a master in impromptus
music, a style of free-form musical composition. Although Piranesi is only
briefly a musician (via a hand-crafted flute), he seems to embody the impromptus
ideology in his everyday existence, coexisting with the House without
questioning its meaning.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;I realised that the search for the Knowledge has encouraged us to think of the
House as if it were a sort of riddle to be unravelled, a text to be
interpreted, and that if ever we discover the Knowledge, then it will be as if
the Value has been wrested from the House and all that remains will be mere
scenery. (Piranesi p. 60)&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Piranesi is resolute in his goal of living in the moment, acting under the
guiding principles of the impromptus. When he comes to this realization, his
character is cemented as a foil to the Other.&lt;&#x2F;p&gt;
&lt;p&gt;I don’t think every statue in the story necessarily relates to Greek or Roman
mythology. Rather, I think they generally pull from powerful symbols or themes
that are recognizable and interpretable by people in general, archetypes that
Joseph Campbell would’ve described as universal to the human experience.&lt;&#x2F;p&gt;
&lt;p&gt;Following this interpretation, the House is sort of a collective unconscious,
filled with archetypes common to the human experience.&lt;&#x2F;p&gt;
&lt;p&gt;The Labyrinth is the human mind, and Piranesi its chronicler.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Revisiting ReScript</title>
        <published>2021-12-11T00:00:00+00:00</published>
        <updated>2021-12-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2021-12-11-revisiting-rescript/"/>
        <id>https://mgmarlow.com/words/2021-12-11-revisiting-rescript/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2021-12-11-revisiting-rescript/">&lt;p&gt;When I first started looking at ReScript two years ago it was a project under
another name: Reason. Derived from OCaml, Reason promised type safe code and
powerful functional primitives without ditching the JavaScript ecosystem. It was
created by Jordan Walke, the creator of React, and boasted adoption in
Facebook&#x27;s core messaging product,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;reasonml.github.io&#x2F;blog&#x2F;2017&#x2F;09&#x2F;08&#x2F;messenger-50-reason&quot;&gt;Facebook Messenger&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;However, Reason was divided between multiple teams with different
responsibilities. Reason itself was a JavaScript-friendly syntax layer sitting
in front of OCaml. The actual compiler, BuckleScript, was maintained by a
separate team with a separate history. The development lifecycles for these two
projects led to an awkward fragmentation of tooling, documentation, and
community.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, BuckleScript and Reason are now united under a common name,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;blog&#x2F;bucklescript-is-rebranding&quot;&gt;ReScript&lt;&#x2F;a&gt;. This
change has brought about a fair deal of controversy, as it poses to further
divide an already niche community with yet another syntax layer.&lt;&#x2F;p&gt;
&lt;p&gt;That aside, the project feels like it&#x27;s in a much healthier state than two years
ago. Unifying the documentation under a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;&quot;&gt;single domain&lt;&#x2F;a&gt; is a non-trivial change that makes
it easier to acclimate to the ecosystem. In addition, the new
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;marketplace.visualstudio.com&#x2F;items?itemName=chenglou92.rescript-vscode&quot;&gt;VSCode extension&lt;&#x2F;a&gt;
goes a long way to make the experience feel at least comparable to TypeScript.&lt;&#x2F;p&gt;
&lt;p&gt;Now&#x27;s the right time to get into ReScript and start exploring some of its killer
features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Great type inference with basically no need for annotations.&lt;&#x2F;li&gt;
&lt;li&gt;No undefined or null. Instead, a robust
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rescript-lang.org&#x2F;docs&#x2F;manual&#x2F;latest&#x2F;null-undefined-option&quot;&gt;option type&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Pattern matching, automatic currying, and other paradigms associated with
functional programming languages.&lt;&#x2F;li&gt;
&lt;li&gt;Designed for JavaScript interop, making it easy to integrate into existing
codebases.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I want to bring particular attention to the last bullet point since it&#x27;s the
most important when discussing real-world codebases. A new toolchain doesn&#x27;t
amount to much if it doesn&#x27;t easily integrate with existing systems.&lt;&#x2F;p&gt;
&lt;p&gt;To test this, I created a prototype with create-react-app and Tailwind (full
code &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mgmarlow&#x2F;rescript-cra&quot;&gt;available here&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;The whole toolchain integrates pretty seamlessly with Webpack, but in retrospect
that&#x27;s not too surprising because the JavaScript output from ReScript is really
clean. Each file is compiled into something that almost resembles code written
by a human.&lt;&#x2F;p&gt;
&lt;p&gt;I was surprised by the guidance to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryyppy&#x2F;rescript-nextjs-template&#x2F;#why-are-the-generated-mjs-files-tracked-in-git&quot;&gt;keep the compiled JavaScript files version-controlled&lt;&#x2F;a&gt;
alongside their ReScript counterparts. Something about committing compiled
output feels fundamentally wrong, even if one of the core contributors of
ReScript is arguing for it. Either way, the implementor can choose which style
they prefer.&lt;&#x2F;p&gt;
&lt;p&gt;My only real gripe with ReScript is the advertisement that it&#x27;s &quot;the fastest
build system on the web&quot;. Now, how can that be possible when
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;esbuild.github.io&#x2F;&quot;&gt;ESBuild&lt;&#x2F;a&gt; exists?&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>What happened to proper tail calls in JavaScript?</title>
        <published>2021-03-27T00:00:00+00:00</published>
        <updated>2021-03-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2021-03-27-proper-tail-calls-js/"/>
        <id>https://mgmarlow.com/words/2021-03-27-proper-tail-calls-js/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2021-03-27-proper-tail-calls-js/">&lt;p&gt;Proper tail calls (PTC) is a programming language feature that enables
memory-efficient recursive algorithms. I&#x27;m not going to belabor the details of
proper tail calls or how it pertains to JavaScript, as
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;2ality.com&#x2F;2015&#x2F;06&#x2F;tail-call-optimization.html&quot;&gt;Dr. Axel&#x27;s article&lt;&#x2F;a&gt;
already offers those explanations. Instead, I&#x27;m going to discuss the evolution
of the feature in JavaScript since its genesis in ECMAScript 2015.&lt;&#x2F;p&gt;
&lt;p&gt;Despite its inclusion in the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;262.ecma-international.org&#x2F;6.0&#x2F;#sec-tail-position-calls&quot;&gt;2015 language specification&lt;&#x2F;a&gt;,
PTC is currently only supported by Safari. It is astounding that six years after
the formal submission of the standard, only one major browser offers any kind of
support.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScriptCore (Safari) ✅
(&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;webkit.org&#x2F;blog&#x2F;6240&#x2F;ecmascript-6-proper-tail-calls-in-webkit&#x2F;&quot;&gt;read all about it&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;V8 (Chrome, Edge, Opera) ❌&lt;&#x2F;li&gt;
&lt;li&gt;SpiderMonkey (Firefox) ❌&lt;&#x2F;li&gt;
&lt;li&gt;Carakan (Opera) ❌&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Why are browser vendors ignoring PTC? V8 chalks it up to two main reasons:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;It makes it more difficult to understand during debugging how execution
arrived at a certain point since the stack contains discontinuities, and&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;JavaScript&#x2F;Reference&#x2F;Global_Objects&#x2F;Error&#x2F;Stack&quot;&gt;error.stack&lt;&#x2F;a&gt;
contains less information about execution flow which may break telemetry
software that collects and analyzes client-side errors.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;It&#x27;s worth noting that V8 fully implemented proper tail calls but ultimately
removed them, according to their
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;v8.dev&#x2F;blog&#x2F;modern-javascript#proper-tail-calls&quot;&gt;blog post&lt;&#x2F;a&gt; from 2016.
To help outline a solution to the aforementioned problems, V8 created a proposal
for an alternative approach called
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tc39&#x2F;proposal-ptc-syntax&quot;&gt;syntactic tail calls&lt;&#x2F;a&gt; (STC). As
the name suggests, it proposes a syntax for opting in to PTC, rather than
supporting implicit opt-in.&lt;&#x2F;p&gt;
&lt;p&gt;The STC proposal is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tc39&#x2F;proposal-ptc-syntax&#x2F;issues&#x2F;23&quot;&gt;hotly&lt;&#x2F;a&gt;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tc39&#x2F;proposal-ptc-syntax&#x2F;issues&#x2F;22&quot;&gt;debated&lt;&#x2F;a&gt;. The arguments
tend to trend in one of two directions:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;STC is unnecessary and defeats the purpose of PTC. Safari has implemented PTC
since 2016 without issue, so most fears of implicit behavior are unwarranted.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;STC is important to guard against potential problems with implicit behavior.
As V8 summarized, PTC introduces educational concerns across developers and
debugging concerns across implementations.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;TC39 editors express a general hesitancy towards STC and a lack of faith for
broader PTC adoption that is
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kangax&#x2F;compat-table&#x2F;issues&#x2F;819#issuecomment-226620936&quot;&gt;well-summarized&lt;&#x2F;a&gt;
by contributor Jordan Harband. Since 2017, the proposal has been
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tc39&#x2F;proposals&#x2F;blob&#x2F;master&#x2F;inactive-proposals.md&quot;&gt;marked inactive&lt;&#x2F;a&gt;.
There is no indication that the proposal will be revived or that PTC adoption
will move in a forward direction.&lt;&#x2F;p&gt;
&lt;p&gt;All that drama aside, browser implementations are only one part of the story.
What about Babel?&lt;&#x2F;p&gt;
&lt;p&gt;Similar to V8, Babel implemented and subsequently reverted both
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;babel&#x2F;babel&#x2F;pull&#x2F;701&quot;&gt;proper tail calls&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;babel&#x2F;babel&#x2F;pull&#x2F;714&quot;&gt;tail call optimization&lt;&#x2F;a&gt;. The reference
to PTC in the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;babeljs.io&#x2F;docs&#x2F;en&#x2F;learn#tail-calls&quot;&gt;ES2015 documentation&lt;&#x2F;a&gt; states,&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Temporarily Removed in Babel 6&lt;&#x2F;p&gt;
&lt;p&gt;Only explicit self referencing tail recursion was supported due to the
complexity and performance impact of supporting tail calls globally. Removed
due to other bugs and will be re-implemented.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;When will tail calls be re-implemented? Not in Babel 7, that&#x27;s for sure. The
uncertain fate of PTC requires JavaScript developers to reach for alternative
approaches.&lt;&#x2F;p&gt;
&lt;p&gt;Kyle Simpson describes the use of a trampoline, a helper function that rewrites
tail-recursive functions with while loops. His methodology is detailed in
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;getify&#x2F;Functional-Light-JS&#x2F;blob&#x2F;master&#x2F;manuscript&#x2F;ch8.md&#x2F;#trampolines&quot;&gt;Chapter 8 of Functional-Light JavaScript&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Those looking for npm packages have a couple of options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;glat.info&#x2F;fext&#x2F;&quot;&gt;fext&lt;&#x2F;a&gt;, a library that enables optimizations by
wrapping functions with its API.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;krzkaczor&#x2F;babel-plugin-tailcall-optimization&quot;&gt;babel-plugin-tailcall-optimization&lt;&#x2F;a&gt;,
a plugin that automatically rewrites tail-recursive functions with
trampolines.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;PS, a final clarification for the pedantic:&lt;&#x2F;p&gt;
&lt;p&gt;The terminology of proper tail calls (PTC) and tail call optimization (TCO) is
often conflated. Here&#x27;s the difference between the two:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;proper tail calls&lt;&#x2F;strong&gt;: functions called in the tail position reuse the current
stack frame, preventing the creation of additional stack frames that cause
space inefficiency.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;tail call optimization&lt;&#x2F;strong&gt;: rewrites a recursive function into an iterative
one, usually by calling goto.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The difference between the two is nuanced, though often mentioned in spicy
Github threads. PTC only deals with stack manipulation, while TCO rewrites a
recursive function as an iterative function. Find more details from Ward
Cunningham&#x27;s &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;wiki.c2.com&#x2F;?TailCallOptimization&quot;&gt;tail call optimization&lt;&#x2F;a&gt;
wiki.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Game design is immaterial</title>
        <published>2021-03-20T00:00:00+00:00</published>
        <updated>2021-03-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2021-03-20-immaterial-game-design/"/>
        <id>https://mgmarlow.com/words/2021-03-20-immaterial-game-design/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2021-03-20-immaterial-game-design/">&lt;p&gt;Reading
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mitpress.mit.edu&#x2F;books&#x2F;elements-game-design&quot;&gt;Elements of Game Design&lt;&#x2F;a&gt;
planted a splinter in my brain that I cannot stop fiddling with. On page 8, when
discussing the game designer&#x27;s role,&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Designer&#x27;s intentions are immaterial, save for how they turned out in the
implementation.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The implication is that design vanishes when the game handed off to the player.
The player doesn&#x27;t directly interact with game design. Instead, the player
experiences with their senses: holding the controller, watching pixels on the
screen, jamming to a chiptune beat.&lt;&#x2F;p&gt;
&lt;p&gt;I find this statement ultimately unsatisfying.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s something in the design itself that lingers; something that continues to
pull at the player&#x27;s expectations long after they&#x27;ve dropped the game.&lt;&#x2F;p&gt;
&lt;p&gt;Take, for example, my love of
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;379720&#x2F;DOOM&#x2F;&quot;&gt;DOOM&lt;&#x2F;a&gt;. The fluidity of
arena-based combat subverted my childhood understanding of first-person shooters
(FPS). Now every FPS I play is subject to lofty expectations stemmed from my
enjoyment of DOOM.&lt;&#x2F;p&gt;
&lt;p&gt;Some games play with expectations more fundamentally within their design.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;store.steampowered.com&#x2F;app&#x2F;219890&#x2F;Antichamber&#x2F;&quot;&gt;Antichamber&lt;&#x2F;a&gt; utilizes
non-Euclidean space to cultivate mind-bending puzzles. The entire experience
hinges on player assumptions that game worlds are reflections of the real world,
and thereby follow the same rules and physics.&lt;&#x2F;p&gt;
&lt;p&gt;This concept of prior experience is referred to in philosophy as
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;A_priori_and_a_posteriori&quot;&gt;a priori&lt;&#x2F;a&gt;, literally
&quot;from the former&quot;. Kant suggests, as quoted by the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;plato.stanford.edu&#x2F;entries&#x2F;kant&#x2F;#TraDed&quot;&gt;Standford Encyclopedia of Philosophy&lt;&#x2F;a&gt;
(emphasis mine):&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;[T]he objective validity of the categories, as a priori concepts, rests on the
facts that through them alone is experience possible (as far as the form of
thinking is concerned). For they then are related necessarily and a priori to
objects of experience, &lt;strong&gt;since only by means of them can any object of
experience be thought at all&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Kant defines a priori concepts as conditions of experience. Every experience I
perceive is rooted in the existence of fundamental concepts that I do not
directly perceive. These fundamental concepts must exist in order for the
experience to exist.&lt;&#x2F;p&gt;
&lt;p&gt;Applied to games, player experience rests upon an a priori foundation of game
design. Player&#x27;s do not interact with game design directly, but the design must
exist in order for the game to exist. Following this transcendental view, the
design exists even though it is unknowable by the player.&lt;&#x2F;p&gt;
&lt;p&gt;What I found while venturing into all of this theory is that, from a player&#x27;s
perspective, &quot;unknowable&quot; is a better definition for game design than
&quot;immaterial&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Although a player only interacts with the sensory aspects of games, the design
doesn&#x27;t vanish from the experience. It&#x27;s still there, built upon as a foundation
of a priori concepts, affecting the player&#x27;s overall enjoyment of the game.&lt;&#x2F;p&gt;
&lt;p&gt;The design, the unknown something, is carried with the player separate from the
game. Players accumulate a repository of design concepts throughout their
lifetime that ultimately affect they way they perceive and experience games.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Creative-consumptive entertainment</title>
        <published>2021-03-17T00:00:00+00:00</published>
        <updated>2021-03-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Graham Marlow
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://mgmarlow.com/words/2021-03-17-creative-consumptive/"/>
        <id>https://mgmarlow.com/words/2021-03-17-creative-consumptive/</id>
        
        <content type="html" xml:base="https://mgmarlow.com/words/2021-03-17-creative-consumptive/">&lt;p&gt;Since discovering
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=20781463&quot;&gt;a discussion on Hacker News&lt;&#x2F;a&gt;
about the perils of consumptive entertainment, I doubt my self-worth every time
I sit down to play a video game. I could be doing something productive. Instead,
I stare at my Steam library, guilt and indecision cycling endlessly.&lt;&#x2F;p&gt;
&lt;p&gt;Forms of &quot;consumptive&quot; entertainment are regularly
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.tjcx.me&#x2F;p&#x2F;consume-less-create-more&quot;&gt;looked down upon&lt;&#x2F;a&gt;, often cited
as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.theminimalists.com&#x2F;create-consume&#x2F;&quot;&gt;necessary evils&lt;&#x2F;a&gt;. The modern
adage goes, &quot;create more, consume less&quot;. Time spent consuming is time better
spent creating, writers opine.&lt;&#x2F;p&gt;
&lt;p&gt;When most people think about entertainment, they tend to group it into one of
two forms:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;consumptive&lt;&#x2F;strong&gt;: no input needed from the participant&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;creative&lt;&#x2F;strong&gt;: consists almost entirely of participant input&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Consumptive entertainment is watching Game of Thrones, scrolling Instagram, or
playing &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.loophero.com&#x2F;&quot;&gt;Loop Hero&lt;&#x2F;a&gt;. Whether or not the participant
chooses to interact doesn&#x27;t change the output of the entertainment. Footage
continues to roll, scrolling remains infinite, and video games play themselves
into eternity.&lt;&#x2F;p&gt;
&lt;p&gt;Creative entertainment is painting a landscape, recording a song, writing a
program. Without the participant actively engaging, nothing is produced.&lt;&#x2F;p&gt;
&lt;p&gt;These two forms of entertainment are often described as being at odds.
Binge-watching is bad, but an occasional episode is a reprieve from a hard day&#x27;s
work. Creativity is good, particularly if years of toil and anguish are
reflected in the final piece.&lt;&#x2F;p&gt;
&lt;p&gt;I propose a new classification of entertainment, one that draws the line between
consumptive and creative.&lt;&#x2F;p&gt;
&lt;p&gt;Creative-consumptive entertainment is consumptive entertainment that is actively
engaged with.&lt;&#x2F;p&gt;
&lt;p&gt;Reading a book with pencil in hand is a great example. Reading is a consumptive
act; both the words and the ideas are static when absorbed in a passive reading
session. However, read with a pencil and you&#x27;ll discover yourself underlining
passages, writing small notes, and engaging with the words in a new, creative
way.&lt;&#x2F;p&gt;
&lt;p&gt;This method of close reading is creative-consumptive because the ideas from the
page are re-interpreted by the reader, adding new meaning and texture.&lt;&#x2F;p&gt;
&lt;p&gt;What&#x27;s most interesting about creative-consumptive entertainment is the ability
for any consumptive form to transform into a creative form by applying
attention. The principles behind close reading carryover to television, movies,
video games, and so on. All it requires is active participation.&lt;&#x2F;p&gt;
&lt;p&gt;I hope that this idea of creative-consumptive entertainment assuages the guilt
that modernity attaches to consumption. Active participation can transform
consumptive entertainment into something that is both more fulfilling and
engaging.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
