<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://mthadley.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mthadley.com/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-01-11T21:52:10+00:00</updated><id>https://mthadley.com/feed.xml</id><title type="html">mthadley</title><subtitle>The personal website of mthadley.</subtitle><author><name>Michael Hadley</name></author><entry><title type="html">Inline Script Dependencies</title><link href="https://mthadley.com/posts/inline-script-dependencies" rel="alternate" type="text/html" title="Inline Script Dependencies" /><published>2026-01-11T00:00:00+00:00</published><updated>2026-01-11T00:00:00+00:00</updated><id>https://mthadley.com/posts/inline-script-dependencies</id><content type="html" xml:base="https://mthadley.com/posts/inline-script-dependencies"><![CDATA[<p>As part of my day job building out a developer platform, I do a lot of
prototyping and testing. These are often multi-step workflows, with an end
user’s browser involved to boot (we do a lot of auth stuff).</p>

<p>I find myself first reaching for Ruby here for several reasons:</p>

<ul>
  <li>Ruby is a comfy language for me.</li>
  <li>Ruby’s standard library… <em>exists</em> (looking at you JavaScript), and built-in
types come with loads of utility.</li>
  <li>And finally—the main thing I wanted to write about—you can <strong>inline the
script’s dependencies</strong>.</li>
</ul>

<p>More on that last piece.</p>

<h2 id="inline-bundler" style="background-image: linear-gradient(58deg, var(--light-blue), var(--pink), var(--blue));">Inline Bundler</h2>

<p>Pretty quickly, I’ll need some third-party gem for the task. For example, maybe
I’m standing up a quick web-server that implements a new
<a href="https://www.rfc-editor.org/rfc/rfc8615"><code class="language-plaintext highlighter-rouge">/.well-known</code></a> endpoint I want to prototype against. <a href="https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html">Bundler makes
this so easy</a>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s2">"bundler/inline"</span>

<span class="n">gemfile</span> <span class="k">do</span>
  <span class="n">source</span> <span class="s2">"https://rubygems.org"</span>

  <span class="n">gem</span> <span class="s2">"puma"</span>
  <span class="n">gem</span> <span class="s2">"rackup"</span>
  <span class="n">gem</span> <span class="s2">"sinatra"</span>
  <span class="n">gem</span> <span class="s2">"sinatra-contrib"</span>
<span class="k">end</span>

<span class="n">get</span> <span class="s2">"/.well-known/oauth-protected-resource"</span> <span class="k">do</span>
  <span class="n">json</span> <span class="ss">resource: </span><span class="s2">"https://example.com/resource"</span><span class="p">,</span>
       <span class="ss">authorization_servers: </span><span class="p">[</span><span class="s2">"https://auth.example.com"</span><span class="p">],</span>
       <span class="ss">bearer_methods_supported: </span><span class="p">[</span><span class="s2">"header"</span><span class="p">]</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Paste that into a <code class="language-plaintext highlighter-rouge">.rb</code> file, run it with <code class="language-plaintext highlighter-rouge">ruby</code>, and you’re off to the races.
Gems are installed <em>somewhere</em> in the background automagically.</p>

<p>I find this pattern really lowers the barrier to experimentation. Of course, we
live in the era of <em>AI</em>, where that barrier has already been steadily dropping.
But even here, small and focused files play well with a coding agent’s limited
context window.</p>

<h2 id="elsewhere" style="background-image: linear-gradient(57deg, var(--light-blue), var(--blue), var(--pink));">Elsewhere</h2>

<p>I wish more tools embraced this pattern. The few I know of:</p>

<ul>
  <li>Nix and <a href="https://nix.dev/manual/nix/2.28/command-ref/nix-shell.html#use-as-a--interpreter"><code class="language-plaintext highlighter-rouge">nix-shell</code> as a shebang can do this</a>, and it’s even
language agnostic.</li>
  <li><code class="language-plaintext highlighter-rouge">uv</code> for Python <a href="https://docs.astral.sh/uv/guides/scripts/#creating-a-python-script">similarly can parse dependencies from a magic comment</a>.
The Astral folks are doing amazing work.</li>
</ul>

<p>Node is the main place I’d like to see this pattern supported, since I spend
much of my time these days writing JavaScript and TypeScript (though I think ESM
imports with Deno may “just work”).</p>

<p>Anyway, hopefully this nudges you (or your agent) toward writing more scripts
that do more than you’d normally expect.</p>

<!-- prettier-ignore-start -->
<!-- prettier-ignore-end -->]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[Bridging the gap between one-off scripts and full-on projects.]]></summary></entry><entry><title type="html">Deletions That Don’t Scale</title><link href="https://mthadley.com/posts/deletions-that-don-t-scale" rel="alternate" type="text/html" title="Deletions That Don’t Scale" /><published>2025-07-27T00:00:00+00:00</published><updated>2025-07-27T00:00:00+00:00</updated><id>https://mthadley.com/posts/deletions-that-don%E2%80%99t-scale</id><content type="html" xml:base="https://mthadley.com/posts/deletions-that-don-t-scale"><![CDATA[<p>I recently discovered that <a href="https://pages.cloudflare.com">Cloudflare Pages</a>, the
service I was using to host this site, seems to be on the path to deprecation.
They want to combine everything into the Workers platform, which I have
experience with, and so is fine by me.</p>

<p>After migrating my site successfully, I was ready for the final step of deleting
my pages project… only to get an error. Something along the lines of “you have
too many deployments, go read
<a href="https://developers.cloudflare.com/pages/platform/known-issues/#delete-a-project-with-a-high-number-of-deployments">this thing</a>.”
Huh?</p>

<blockquote>
  <p>You may not be able to delete your Pages project if it has a high number
(over 100) of deployments. The Cloudflare team is tracking this issue.</p>
</blockquote>

<p>Turns out you gotta download a random Node script the Cloudflare team wrote,
inject it with some API credentials, and do a slow deletion yourself.</p>

<p>On one hand, this is kinda annoying. Can’t a big company at Cloudflare find a
team to implement the slow background deletion job?</p>

<p>On the other hand, why optimize for a relatively uncommon case that is likely
associated with churn anyways? The old wisdom of
<a href="https://paulgraham.com/ds.html">do the things that don’t scale</a> applies. I
can’t be too mad; even with all of the sleeps in the script, it still only took
a few extra minutes of my time, and I’ll likely never need to delete a Pages
project again.</p>]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[Cloudflare made me run a hacky script.]]></summary></entry><entry><title type="html">Two Years of Kagi</title><link href="https://mthadley.com/posts/two-years-of-kagi" rel="alternate" type="text/html" title="Two Years of Kagi" /><published>2025-07-12T00:00:00+00:00</published><updated>2025-07-12T00:00:00+00:00</updated><id>https://mthadley.com/posts/two-years-of-kagi</id><content type="html" xml:base="https://mthadley.com/posts/two-years-of-kagi"><![CDATA[<p>I started paying for <a href="https://kagi.com">Kagi</a> back in 2023, and while I briefly
<a href="/posts/kagi-apple-shortcut">alluded to my usage</a> of their
service last year, I’ve yet to write up my thoughts after remaining a customer.
With my two year subscription-versary coming up, now seems as good a time as
any.</p>

<h2 id="search-like-you-remember-it" style="background-image: linear-gradient(56deg, var(--pink), var(--light-blue), var(--blue));">Search Like You Remember It</h2>

<p>Kagi ostensibly exists to be a
non-<a href="https://en.wikipedia.org/wiki/Enshittification">enshittified</a> Google and in
my experience succeeds in that purpose. I buy their marketing that removing
perverse incentives from ad revenue and replacing it with a subscription fee
unburdens them to focus on a friendly search experience.</p>

<p>Having no ads in search results is potentially worth paying for on its own,
despite being somewhat undercut by <a href="https://duck.com">DuckDuckGo</a>, who will let
you disable advertisements for free. For example, a search for <em>“Ford”</em> on Kagi
had the automaker right at the top, with Google nearly having it below the fold
underneath a mountain of sponsored results. Yikes.</p>

<p>The quality of those search results are certainly no worse than other
competitors, if not partly because
<a href="https://help.kagi.com/kagi/search-details/search-sources.html">they often come from the same place</a>.
In reality, the results are better thanks to features like
<a href="https://kagi.com/stats?stat=leaderboard">customizable rankings</a>; I haven’t seen
a W3Schools link in <em>ages</em> after lowering its ranking. This might be Kagi’s
killer feature for me.</p>

<h2 id="new-ideas" style="background-image: linear-gradient(32deg, var(--pink), var(--blue), var(--light-blue));">New Ideas</h2>

<p>While Kagi does offer their version of the
<a href="https://arstechnica.com/ai/2025/03/ai-search-engines-give-incorrect-answers-at-an-alarming-60-rate-study-says/">controversial “AI Search Summary”</a>,
you won’t see it unless you ask for it. I think the activation method of
appending a question mark to your query is clever, such as “What does IMF stand
for?”. I do use them when I know there’s a short, well-known answer and I don’t
want to click through some random site to get it.</p>

<p><a href="https://help.kagi.com/kagi/features/lenses.html">Lenses</a> are a cool idea,
though I have mixed feelings about them. I use the “Forum” lens <em>all the damn
time</em>, but if I’m honest, it feels like a glorified shortcut for
<code class="language-plaintext highlighter-rouge">site:reddit.com</code>. Otherwise, I don’t feel like I’m missing much by sticking
with “All” results.</p>

<p><a href="https://help.kagi.com/kagi/features/bangs.html">Bangs</a> are great too.
<a href="https://duckduckgo.com/bangs">They aren’t unique to Kagi</a>, but Kagi has a great
implementation. Append <code class="language-plaintext highlighter-rouge">!i</code> to your query to search for images, <code class="language-plaintext highlighter-rouge">!yt</code> for
YouTube results, or my favorite, a lone <code class="language-plaintext highlighter-rouge">!</code> to skip the search page entirely and
go straight to the first result. The latter feels like a feature you’d never see
from Google who <em>needs</em> you to see their search page or else their business
model falls apart.</p>

<h2 id="complaints" style="background-image: linear-gradient(30deg, var(--light-blue), var(--blue), var(--pink));">Complaints</h2>

<p>In general, I’m a happy Kagi customer and I have no plans to switch back to a
free search provider like Google (or more likely, DuckDuckGo). However, I do
have a few minor complaints.</p>

<p>In addition to search, Kagi has been releasing a gaggle of other adjacent
products: <a href="https://translate.kagi.com">Translations</a>,
<a href="https://help.kagi.com/kagi/features/maps.html">Maps</a>,
<a href="https://help.kagi.com/kagi/ai/assistant.html">AI chat</a>, and even a
<a href="https://kagi.com/orion/">browser</a>. I can certainly understand the strategy of
trying to replicate any missing features that may lead users back to a
competitor like Google, but they come across as half-baked. Kagi feels like the
product of a small team, so I worry about their ability to be competitive in all
of these areas, when all I really want from them is the best paid search that I
can buy.</p>

<p><a href="https://help.kagi.com/kagi/search-details/search-speed.html">Kagi is certainly proud of their search speed</a>,
and while I feel it is generally adequate, Google still <em>renders</em> faster for me.
Occasionally there will be notable delay of up to a few seconds before I see
Kagi’s results (maybe waiting on upstream search result provider APIs), with
Google consistently feeling instantaneous. Admittedly, the end-to-end experience
is still in Kagi’s favor, as I don’t need to mentally filter out invasive ad
results.</p>

<h2 id="should-you-pay-for-search" style="background-image: linear-gradient(57deg, var(--pink), var(--light-blue), var(--blue));">Should You Pay for Search?</h2>

<p>Honestly, I don’t know! For most people, probably not. Unless you’re like me, on
a computer 8 hours a day, constantly interacting with a search engine, I think
you are not likely to see the money as well spent.</p>

<p>However, if you’re at all sick of the Google’s new status quo, you may as well
give their <a href="https://kagi.com/pricing">free trial</a> a test drive.</p>]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[Some thoughts after (no, really) paying for search.]]></summary></entry><entry><title type="html">Mistyping My Own Name</title><link href="https://mthadley.com/posts/mistyping-my-own-name" rel="alternate" type="text/html" title="Mistyping My Own Name" /><published>2025-04-06T00:00:00+00:00</published><updated>2025-04-06T00:00:00+00:00</updated><id>https://mthadley.com/posts/mistyping-my-own-name</id><content type="html" xml:base="https://mthadley.com/posts/mistyping-my-own-name"><![CDATA[<p>Yesterday I was setting up a new computer for work. I use
<a href="https://nixos.org">Nix</a> to manage all of the configuration for my development
environment so one of the steps is to run a script to bootstrap everything. It
was all going great until I encountered an error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cannot find home folder: /Users/michale.hadley
</code></pre></div></div>

<p><em>Cue dramatic zoom on <strong>michale</strong>.hadley</em>. Turns out during the initial setup I
had, well, mistyped my own name.</p>

<p>What am I supposed to do now? I’ve never had to change a username on MacOS
before and it doesn’t seem trivial. What might break? Should I nuke everything
and start from scratch? Is the more pragmatic thing to just become <em>Michale</em>
Hadley from now on? Do I immortalize this mistake forever
<a href="https://github.com/mthadley/dotfiles/blob/8c9976645f0ab4bf3f7b3da1b0852a88cb3837d7/flake.nix#L54">in my Nix configuration?</a>.</p>

<p>After a little frustration, turns out there’s a way out of this:</p>

<ol>
  <li>You can’t directly edit your own username, at least in the MacOS settings
GUI. So first create a new admin user and log in as them.</li>
  <li>Now in the Settings the <em>username</em> field is editable. So change that.</li>
  <li>Don’t be like me and immediately try to log in as the original user; MacOS
will create a new home folder using the new name and then hang on the sign-in
screen. Instead, rename the home folder in Finder, and then select it as the
user’s home folder in the GUI.</li>
</ol>

<p>So in the end I managed to correct my mistake. Initially Nix complained about
not finding the old, typo-ed home folder, but that was fixed by deleting a
<code class="language-plaintext highlighter-rouge">.nix-profile</code> symlink.</p>

<p>I’m still a little spooked about what gremlins may be hiding inside of a user
account who had at one time had their username changed in a very janky feeling
way. I guess I’ll find out.</p>]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[As I am wont to do]]></summary></entry><entry><title type="html">Akinator versus AI</title><link href="https://mthadley.com/posts/akinator-vs-ai" rel="alternate" type="text/html" title="Akinator versus AI" /><published>2025-02-01T00:00:00+00:00</published><updated>2025-02-01T00:00:00+00:00</updated><id>https://mthadley.com/posts/akinator-vs-ai</id><content type="html" xml:base="https://mthadley.com/posts/akinator-vs-ai"><![CDATA[<p>The other night I was having trouble sleeping and my mind wandered to, of all
the things, that old Akinator game that was popular in the mid-aughts. Amazingly
it’s <a href="https://en.akinator.com">still online</a>, and looks like there’s even mobile
apps, but my memory was of playing it on the web.</p>

<p>If you don’t know Akinator, it’s basically a bot that tries to guess a
character you are thinking of based on answers to yes or no questions.
Ostensibly a computer playing twenty questions, or so I’ve read. When I was
younger Akinator blew my mind, though my older programmer self is less
impressed. Not that I could do a better job or anything.</p>

<p>So in a fit of nostalgia I fired up Akinator and gave it a softball of <em>Luke
Skywalker</em> and it did… kinda badly? It got the answer in roughly 40 questions,
and some of the questions were <em>interesting</em>:</p>

<blockquote>
  <p>Does your character play in ‘Harry ‘Potter’?”</p>
</blockquote>

<p>I guess there’s a lot of Potter fans out there such that Akinator needs to lead
with this one.</p>

<blockquote>
  <p>“Is your character a famous YouTuber?”</p>
</blockquote>

<p>YouTube was not (or maybe barely) a thing when I first played Akinator, but
understandable. I wonder how many times it guesses “Mr. Beast”.</p>

<blockquote>
  <p>“Does your character talk about basketball?”</p>
</blockquote>

<p>It asked this question <em>after</em> I had already affirmed that I was thinking of a
Star Wars character. Do I now know something about Yoda?</p>

<p>And really a bunch of other odd ones. It first guessed <em>Anakin Skywalker</em>
around, and after continuing it landed on <em>Lego</em> Luke Skywalker, which I gave
it.</p>

<h2 id="the-competition" style="background-image: linear-gradient(50deg, var(--pink), var(--light-blue), var(--blue));">The Competition</h2>

<p>That was fun, but not as impressive as I remember. It got me thinking, with the
AI “revolution” in full-swing, would any old LLM do better than Akinator
nowadays? I decided to do an experiment, and since I’m a Kagi Assistant user,
try an Akinator prompt across a range of different models:</p>

<blockquote>
  <p>You are Akinator, a mind-reading genie. I will think of a character and you
will try to guess that character based on my answers to your yes or no
questions.</p>

  <p>As you ask each question, number them so that it is easy to see how many
questions were asked. Instead of asking a question, you may guess the
character, but you only have three guesses before you lose the game. You may
ask as many questions as you like before guessing.</p>
</blockquote>

<p>Here’s how well they each did. Fewer guesses and questions the better. All
played with “Luke Skywalker” as the same answer in mind.</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Outcome</th>
      <th>Guesses</th>
      <th>Questions</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>DeepSeek Chat V3</td>
      <td>✅</td>
      <td>1</td>
      <td>6</td>
    </tr>
    <tr>
      <td>Nova Pro</td>
      <td>✅</td>
      <td>1</td>
      <td>7</td>
    </tr>
    <tr>
      <td>GPT 4o</td>
      <td>✅</td>
      <td>1</td>
      <td>11</td>
    </tr>
    <tr>
      <td>Claude 3 Opus</td>
      <td>✅</td>
      <td>1</td>
      <td>11</td>
    </tr>
    <tr>
      <td>Llama 3.3 70B</td>
      <td>✅</td>
      <td>1</td>
      <td>12</td>
    </tr>
    <tr>
      <td>Claude 3.5 Haiku</td>
      <td>✅</td>
      <td>1</td>
      <td>13</td>
    </tr>
    <tr>
      <td>GPT 4o mini</td>
      <td>✅</td>
      <td>3</td>
      <td>18</td>
    </tr>
    <tr>
      <td>Claude 3.5 Sonnet</td>
      <td>✅</td>
      <td>3</td>
      <td>19</td>
    </tr>
    <tr>
      <td>Gemini Pro</td>
      <td>✅</td>
      <td>3</td>
      <td>35</td>
    </tr>
    <tr>
      <td>Llama 3.1 405B</td>
      <td>❌</td>
      <td>3</td>
      <td>23</td>
    </tr>
    <tr>
      <td>Nova Lite</td>
      <td>❌</td>
      <td>3</td>
      <td>24</td>
    </tr>
    <tr>
      <td>Mistral Pixtral</td>
      <td>❌</td>
      <td>3</td>
      <td>26</td>
    </tr>
    <tr>
      <td>Mistral Large</td>
      <td>❌</td>
      <td>3</td>
      <td>57</td>
    </tr>
    <tr>
      <td>Qwen QwQ 32b</td>
      <td>❔</td>
      <td>0</td>
      <td>208</td>
    </tr>
  </tbody>
</table>

<p>Clearly many of the LLMs out-akinatored Akinator. I didn’t save any of the
transcripts but if you’re interested I’d say it’s more fun to just try the above
prompt yourself. Here are some of the quirks of each model I noticed:</p>

<ul>
  <li>As if to echo the
<a href="https://www.theverge.com/2025/1/27/24353391/nvidias-market-cap-drops-by-almost-600-billion-amid-deepseek-r1-hype">market chaos</a>
caused by the launch of R1, <strong>DeepSeek Chat V3</strong> literally did feel like it
read my mind, coming out on top with only 6 questions asked.
    <ul>
      <li>I repeated but with Po from <em>Kung Fu Panda</em> as the character, which it
failed with only 8 questions. Its guesses were Chewbacca, Groot, and Smaug.</li>
    </ul>
  </li>
  <li><strong>Sonnet</strong> did the worst of the Claude models, which surprised since I
expected it to be the strongest of the three.</li>
  <li><strong>Mistral Pixtral</strong> had a novel (bad) strategy for elimination: Alphabetical.
    <ul>
      <li><em>“Is your character’s first name a single syllable?”</em></li>
      <li><em>“Is your character’s first letter in his name between A and M?”</em></li>
      <li><em>“Does your character’s first name have 4 letters?”</em></li>
    </ul>
  </li>
  <li><strong>Mistral Large</strong> took the longest to finally lose. It hilariously mentioned
the answer in one of its questions, but never actually guessed it: <em>“Is the
character’s name two words long? For example, ‘Han Solo’ or ‘Luke
Skywalker.’“</em>
    <ul>
      <li>While many of the models would repeat questions, the Mistral models had the
worst habit, with Pixtral even repeating guesses with “Harry Potter” twice.</li>
    </ul>
  </li>
  <li><strong>Gemini Pro</strong> asked a real tricky one: “Is your character from a Disney
movie?”. I answered yes (they technically own Star Wars now) but I think it
made things more confusing for Gemini (Aladdin ended up being a guess).</li>
  <li>The <strong>Llamas</strong> kept a running summary of what it had learned so far before
asking each question: <em>“So the character is a male human who is fictional,
primarily from a movie released before 2000, plays a role in a well-known
science fiction franchise, and is the main protagonist of that franchise. That
helps a lot. Here’s my next question: […]”.</em></li>
  <li>The <strong>Qwen</strong> model took the “You may ask as many questions as you like”
provision in my prompt quite literally, with its initial response producing
several hundred questions and counting before I assume hitting some kind of
limit and cutting off. It goes without saying that I did not attempt to answer
Qwen’s giant question dump and instead disqualified it, though maybe I’m just
a bad prompt engineer.</li>
  <li><strong>Nova Lite</strong> got caught in some bizarre binary search train of thought where
it kept asking if the character was “from a movie released in the past 5
years?”, “past 2 years?”, “past year?”, “past 6 months?”, down to “past hour?”
    <ul>
      <li>When it finally did guess, it did so in a sneaky way: <em>“My first guess is:
The character you are thinking of is not a real person, nor is it a
character from a popular fantasy or science fiction movie or TV show
released in the last hour.”</em></li>
    </ul>
  </li>
  <li>In a complete turnaround from Lite, <strong>Nova Pro</strong> nailed it with Luke in only 7
questions, coming in second place.</li>
</ul>

<p>At times I worried I had led the model down a bad road with an answer to a
wishy-washy question. While I’m certainly a factor in this “experiment”, I
decided it’s still fair to judge the model for asking the question in the first
place.</p>

<p>Worth noting that I’m not an expert on AI models, and so this may have been a
poor test for some of these. Like if <em>Star Wars</em> material never appears in any
of its training data, it’s probably very hard to ever get the right answer.</p>]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[Pitting the old genie against modern opponents]]></summary></entry><entry><title type="html">Quarantining Duplicates</title><link href="https://mthadley.com/posts/quarantining-duplicates" rel="alternate" type="text/html" title="Quarantining Duplicates" /><published>2025-01-21T00:00:00+00:00</published><updated>2025-01-21T00:00:00+00:00</updated><id>https://mthadley.com/posts/quarantining-duplicates</id><content type="html" xml:base="https://mthadley.com/posts/quarantining-duplicates"><![CDATA[<p>I’ve run into the following problem multiple times now at different workplaces:</p>

<p>I have a database table with some column having rows with duplicate values, when
they should have been unique. Think like an <code class="language-plaintext highlighter-rouge">external_id</code>, <code class="language-plaintext highlighter-rouge">email</code>, or <code class="language-plaintext highlighter-rouge">url</code>.
Maybe someone forgot to add a unique index originally, or perhaps uniqueness
being a requirement for the problem domain was discovered later in the table’s
life.</p>

<p>Either way, I have duplicate rows. Let’s say it’s not obvious how to clean
things up, but in the meantime while we figure that out, I at least want to
“stop the bleeding”. Prevent any new duplicates from being inserted.</p>

<p>Since I’ve been working with Postgres for a while now, I’ve been meaning to try
the following plan the next time I’m in this situation.</p>

<h2 id="step-1-add-new-flag-column" style="background-image: linear-gradient(52deg, var(--light-blue), var(--blue), var(--pink));">Step #1: Add New Flag Column</h2>

<p>First, add a new column that will serve to mark these existing duplicates as
“known”:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">some_table</span>
<span class="k">ADD</span> <span class="k">COLUMN</span> <span class="n">known_duplicate</span> <span class="nb">BOOLEAN</span>
<span class="k">DEFAULT</span> <span class="k">false</span><span class="p">;</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">DEFAULT</code> is important for later, as we’ll never want new rows to be marked
as a duplicate, since their uniqueness will be enforced.</p>

<h2 id="step-2-backfill-the-flag" style="background-image: linear-gradient(42deg, var(--blue), var(--pink), var(--light-blue));">Step #2: Backfill the Flag</h2>

<p>At this point every row will have <code class="language-plaintext highlighter-rouge">known_duplicate</code> set to <code class="language-plaintext highlighter-rouge">FALSE</code>. We need to
find each set of duplicate rows and set all of their <code class="language-plaintext highlighter-rouge">known_duplicate</code> columns
to <code class="language-plaintext highlighter-rouge">TRUE</code>, <em>except for one of them</em>.</p>

<p>Having <code class="language-plaintext highlighter-rouge">known_duplicate</code> set to <code class="language-plaintext highlighter-rouge">FALSE</code> for one of the rows allows it to
participate in the index added in the next step, preventing any new rows being
added to that duplicate set. Deciding which row to pick will be specific to your
problem domain, but there will be an obvious “primary” row that your application
is already preferring in some way for that duplicate column.</p>

<h2 id="step-3-add-the-partial-index" style="background-image: linear-gradient(60deg, var(--light-blue), var(--blue), var(--pink));">Step #3: Add the Partial Index</h2>

<p>Finally, add the <code class="language-plaintext highlighter-rouge">UNIQUE</code> index, but instead of having it cover all rows like
you wish it originally did, it’ll need to be a
<a href="https://www.postgresql.org/docs/current/indexes-partial.html"><em>partial index</em></a>
over the rows that aren’t a known duplicate:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">UNIQUE</span> <span class="k">INDEX</span> <span class="n">partial_idx_on_some_table</span>
<span class="k">ON</span> <span class="n">some_table</span> <span class="p">(</span><span class="n">wish_it_was_unique</span><span class="p">)</span>
<span class="k">WHERE</span> <span class="k">NOT</span> <span class="n">known_duplicate</span><span class="p">;</span>
</code></pre></div></div>

<p>Now your set of duplicates can’t grow any larger, allowing you to slow down and
figure out your next steps. Like how there were duplicates in the first place
(race condition, bad data import, etc.), and how you’ll de-duplicate them.</p>

<p>Or at least that’s the idea. Has anyone tried something like this? Is there a
better way? If anyone is reading this, I’d love to know.</p>]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[Proposing a plan to add a unique index to a table with existing duplicate values.]]></summary></entry><entry><title type="html">Recent Travels</title><link href="https://mthadley.com/posts/recent-travels-2024" rel="alternate" type="text/html" title="Recent Travels" /><published>2024-12-25T00:00:00+00:00</published><updated>2024-12-25T00:00:00+00:00</updated><id>https://mthadley.com/posts/recent-travels-2024</id><content type="html" xml:base="https://mthadley.com/posts/recent-travels-2024"><![CDATA[<p>My past year, more than most, had a strong theme of travel, so I wanted to share
a few pictures and thoughts about each of the places I visited. Nothing too in
depth, but just a quick overview for posterity, in chronological order.</p>

<h2 id="sedona" style="background-image: linear-gradient(38deg, var(--blue), var(--light-blue), var(--pink));">Sedona</h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/sedona-600-674da1d90.webp 600w, /assets/images/recent-travels-2024/sedona-900-674da1d90.webp 900w, /assets/images/recent-travels-2024/sedona-1200-674da1d90.webp 1200w, /assets/images/recent-travels-2024/sedona-1500-674da1d90.webp 1500w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/sedona-600-e6175f871.jpg 600w, /assets/images/recent-travels-2024/sedona-900-e6175f871.jpg 900w, /assets/images/recent-travels-2024/sedona-1200-e6175f871.jpg 1200w, /assets/images/recent-travels-2024/sedona-1500-e6175f871.jpg 1500w" type="image/jpeg" /><img src="/assets/images/recent-travels-2024/sedona-600-e6175f871.jpg" alt="One of the famous Sedona buttes, looming over a neighborhood." width="4936" height="3296" /></picture>

<p>We started the year with a road trip out to Sedona to visit some family who had
recently moved there. It was my first time in Sedona, and the natural beauty of
the iconic red rocks exceeded my expectations.</p>

<p>A few things I did not expect before visiting:</p>

<ul>
  <li>Sedona is a
<a href="https://darksky.org/news/ida-designates-sedona-arizona-the-worlds-eighth-international-dark-sky-community/">“dark sky” community</a>.
There’s very little light pollution, which makes it great for stargazing. But
with almost no public lighting, man is it dark at night, and navigating around
after sunset can be tough if you’re just arriving.</li>
  <li>No offense to the residents, but the vibe of the town is a little bit that of
a retirement community. Lots of older folks, though to their credit, they all
appear to be very active. I suppose you don’t choose to retire there unless
you love the outdoors, hiking, mountain biking, and so forth.</li>
</ul>

<h2 id="lisbon" style="background-image: linear-gradient(41deg, var(--blue), var(--pink), var(--light-blue));">Lisbon</h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/lisbon-600-d1872f0f0.webp 600w, /assets/images/recent-travels-2024/lisbon-900-d1872f0f0.webp 900w, /assets/images/recent-travels-2024/lisbon-1200-d1872f0f0.webp 1200w, /assets/images/recent-travels-2024/lisbon-1500-d1872f0f0.webp 1500w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/lisbon-600-267ebd15d.jpg 600w, /assets/images/recent-travels-2024/lisbon-900-267ebd15d.jpg 900w, /assets/images/recent-travels-2024/lisbon-1200-267ebd15d.jpg 1200w, /assets/images/recent-travels-2024/lisbon-1500-267ebd15d.jpg 1500w" type="image/jpeg" /><img src="/assets/images/recent-travels-2024/lisbon-600-267ebd15d.jpg" alt="An old castle snaking up the hills above Sintra, Portugal." width="4032" height="3024" /></picture>

<p>When it comes to company on-sites<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, my employer has been very generous with
the choice of locations. We do two every year and the first for 2024 was Lisbon.
Very interesting, lively, and old city. Supposedly one of the oldest in Europe.</p>

<p>The highlight was touring the nearby town of Sintra, seeing a <em>castle</em> atop some
far away hill, and spontaneously hiking to the top with some coworkers.
Definitely the single most medieval thing I’ve seen.</p>

<h2 id="milan" style="background-image: linear-gradient(59deg, var(--light-blue), var(--blue), var(--pink));">Milan</h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/milan-600-bfce1204f.webp 600w, /assets/images/recent-travels-2024/milan-900-bfce1204f.webp 900w, /assets/images/recent-travels-2024/milan-1200-bfce1204f.webp 1200w, /assets/images/recent-travels-2024/milan-1500-bfce1204f.webp 1500w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/milan-600-5e5eed603.jpg 600w, /assets/images/recent-travels-2024/milan-900-5e5eed603.jpg 900w, /assets/images/recent-travels-2024/milan-1200-5e5eed603.jpg 1200w, /assets/images/recent-travels-2024/milan-1500-5e5eed603.jpg 1500w" type="image/jpeg" /><img loading="lazy" src="/assets/images/recent-travels-2024/milan-600-5e5eed603.jpg" alt="View from the Duomo looking out at the saint-topped spires." width="4032" height="3024" /></picture>

<p>We were invited to a wedding being held at the nearby Lake Como and took the
opportunity to spend a few days in Milan as well. While Milan tends to be lower
on the list of cities to visit in Italy, I still enjoyed its more everyday,
business atmosphere. There aren’t as many must-see landmarks, but the best is
the Duomo which dominates the city center. It’s impressive as one of the largest
Cathedrals you’ll ever see.</p>

<h2 id="sonoma" style="background-image: linear-gradient(49deg, var(--pink), var(--light-blue), var(--blue));">Sonoma</h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/sonoma-600-6debeb16e.webp 600w, /assets/images/recent-travels-2024/sonoma-900-6debeb16e.webp 900w, /assets/images/recent-travels-2024/sonoma-1200-6debeb16e.webp 1200w, /assets/images/recent-travels-2024/sonoma-1500-6debeb16e.webp 1500w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/sonoma-600-a8678660f.jpg 600w, /assets/images/recent-travels-2024/sonoma-900-a8678660f.jpg 900w, /assets/images/recent-travels-2024/sonoma-1200-a8678660f.jpg 1200w, /assets/images/recent-travels-2024/sonoma-1500-a8678660f.jpg 1500w" type="image/jpeg" /><img loading="lazy" src="/assets/images/recent-travels-2024/sonoma-600-a8678660f.jpg" alt="Rows of wine-barrels at a winery in Sonoma." width="4006" height="3004" /></picture>

<p>Sonoma was the second of the biannual work on-sites. Despite being a long-time
resident of (southern) California, it was my first time in wine country. Not as
novel as some of the other destinations this year, but still a great time to
catch up with co-workers, and the short flight was certainly appreciated.</p>

<h2 id="japan" style="background-image: linear-gradient(55deg, var(--pink), var(--light-blue), var(--blue));">Japan</h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/japan-600-27b544124.webp 600w, /assets/images/recent-travels-2024/japan-900-27b544124.webp 900w, /assets/images/recent-travels-2024/japan-1200-27b544124.webp 1200w, /assets/images/recent-travels-2024/japan-1500-27b544124.webp 1500w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/recent-travels-2024/japan-600-f888e7f66.jpg 600w, /assets/images/recent-travels-2024/japan-900-f888e7f66.jpg 900w, /assets/images/recent-travels-2024/japan-1200-f888e7f66.jpg 1200w, /assets/images/recent-travels-2024/japan-1500-f888e7f66.jpg 1500w" type="image/jpeg" /><img loading="lazy" src="/assets/images/recent-travels-2024/japan-600-f888e7f66.jpg" alt="A shrine with a yellow gingko tree." width="4936" height="3296" /></picture>

<p>The last big trip of the year was Japan, with most of it spent in Tokyo, and a
small excursion to nearby Hakone. This was a trip we had long been hoping to
make, it has been one of the best.</p>

<p>Tokyo is such a singular place, and very different than any other I’ve been
(especially as my first time in Asia as a whole). Very clean and orderly, while
at the same time crazy with so much happening in any direction you look. Tokyo
was full of great food and small shops to visit. I think we only scratched the
surface.</p>

<p>On our way to Hakone I experienced the famous Japanese love of trains. Starting
with a pretty typical regional line, which continued with transfers to
increasingly more specialized cars and tracks, shorter switch-back following
cars on the way to Gora station, and cable-cars pulling themselves up steep
inclines. Impressive stuff.</p>

<p>No doubt we’ll be back to Japan with so many other places to visit.</p>

<h2 id="next-year" style="background-image: linear-gradient(36deg, var(--pink), var(--blue), var(--light-blue));">Next Year</h2>

<p>I’m hoping we’ll keep this travel streak up next year, so I may post some more
about those, though maybe closer to when they happen rather than a roundup like
this one. Until then.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">

      <p>On-sites? Off-sites? What’s the right term when the company is entirely
remote? There’s no dedicated “site” either way. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[A year-end review of places I recently visited.]]></summary></entry><entry><title type="html">Game Anniversaries</title><link href="https://mthadley.com/posts/game-anniversaries" rel="alternate" type="text/html" title="Game Anniversaries" /><published>2024-11-22T00:00:00+00:00</published><updated>2024-11-22T00:00:00+00:00</updated><id>https://mthadley.com/posts/game-anniversaries</id><content type="html" xml:base="https://mthadley.com/posts/game-anniversaries"><![CDATA[<p>Half-Life 2 celebrates 20 years since release, and Warcraft with 30. Both truly
loom large in my video game memories, and so I had a few thoughts about each.</p>

<h2 id="half-life" style="background-image: linear-gradient(48deg, var(--blue), var(--light-blue), var(--pink));">Half-Life</h2>

<p>Hot on the heels of the
<a href="https://www.half-life.com/en/halflife/25th">25th anniversary of the original Half-Life last year</a>,
Valve gave its sequel similar treatment; an
<a href="https://www.half-life.com/en/halflife2/20th">awesome micro-site</a>, a
<a href="https://www.youtube.com/watch?v=YCjNT9qGjh4">two hour documentary</a><sup id="fnref:documentary"><a href="#fn:documentary" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>,
and patches that keep the game running well on modern systems.</p>

<p>I think this is such a fantastic way to treat a beloved series like Half-Life.
Everything about it is in service of the fans, the original developers, and the
history of the game. They even consolidated the original release with the two
episodes into a single game on Steam and then gave the whole thing away <em>for
free</em> (at least for the week). I’m glad Valve has resisted the recent “remaster”
craze, or tried to milk more money out of these old games like other studios
(this will come back later).</p>

<p>I’ve started replaying through Half-Life 2 and was thrilled to see the return of
the Valve commentary mode. I recall it first being added all the way back in
2007’s <a href="https://www.teamfortress.com">Team Fortress 2</a>. Just as before I find it
very enjoyable to hear from the developers the challenges and considerations
that went into making the game. I appreciate these anecdotes even more as I’ve
since embarked on a soon-to-be 15 year career in software myself.</p>

<p>Everything about how Valve chose to celebrate Half-Life 2 was great.
Coincidentally, it happened around the same time as Warcraft, and I couldn’t
help but notice the difference.</p>

<h2 id="warcraft" style="background-image: linear-gradient(53deg, var(--light-blue), var(--blue), var(--pink));">Warcraft</h2>

<p>The Warcraft series turned 30 with the original Warcraft strategy game releasing
all the way back in 1994. It was one of the first PC games I ever played, with
its sequel, Warcraft 2: Tides of Darkness, gobbling tons of hours during my
grade-school years.</p>

<p>Like many kids with access to a home computer back then, these early strategy
games are what turned me into a huge Warcraft fan. I moved on to Warcraft 3, and
then World of Warcraft, the latter dominating my free time as a teenager. I
occasionally played their other games like Diablo and Starcraft, but <em>Warcraft</em>
was the standout for me. Which made it all the more unfortunate when I was
watching Blizzard’s
<a href="https://www.youtube.com/watch?v=0rEfOTxr59o">“Warcraft 30th Anniversary Direct” on YouTube</a>.</p>

<p>Everything about the presentation served to reiterate the heavily corporate
nature of modern <em>Activision</em> Blizzard. Yes, there was plenty of talk about the
impact of the series and testimonials by long time fans. However the stilted and
hollow delivery by executives talking about <em>why</em> I should be excited about the
new worlds (products) I could explore (buy) reminded why most long-time fans
have moved on.</p>

<p>Maybe my glasses are overly rose-tinted, but it felt like the old Blizzard
didn’t need to do so much telling; <em>showing</em> us the game was enough to capture
our attention, and dollars by extension.</p>

<p>Granted, I really do think it’s cool they’ve made the old titles like Warcraft 1
and 2 playable again on new PCs.
 Of course
<a href="https://us.shop.battle.net/en-us/product/warcraft-2-remastered">it had to be a remaster</a>,
providing a means to charge
<a href="https://us.shop.battle.net/en-us/product/warcraft-remastered-battle-chest">$40 for 25 year old games</a>.
Admittedly, a new old stock Battle Chest lingering on store shelves at an outdated price
is really its own form of Blizzard homage and nostalgia.</p>

<h2 id="moving-on" style="background-image: linear-gradient(57deg, var(--blue), var(--light-blue), var(--pink));">Moving On</h2>

<p>Blizzard is a different company than it used to be, and by contrast we’ve been
lucky that Valve has somehow stayed the same for so long. Maybe it’s the
difference of high growth<sup id="fnref:blizzard_employees"><a href="#fn:blizzard_employees" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> versus
<a href="https://realbusiness.co.uk/valve-software-how-a-company-became-more-profitable-per-head-than-apple-or-google">staying small</a>.
Maybe one day <a href="https://en.wikipedia.org/wiki/Gabe_Newell">Gaben</a> will step down
and Microsoft will swoop in with acquisition money, but until then I’m glad
Valve is still doing things their way.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:documentary">

      <p>In one scene the interviewer can be heard asking a question and the voice
sounds like one of the founders of
<a href="https://www.youtube.com/@NoclipDocs">Noclip</a>, which would make a lot of
sense. They make awesome video game documentaries and would be a great
choice on Valve’s part to produce this kind of thing. <a href="#fnref:documentary" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:blizzard_employees">

      <p>Blizzard has 13,000 employees as of 2022, at least according to
<a href="https://en.wikipedia.org/wiki/Blizzard_Entertainment">Wikipedia</a>. <a href="#fnref:blizzard_employees" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[Just another reminder how old I'm getting...]]></summary></entry><entry><title type="html">Games of Summer 2024</title><link href="https://mthadley.com/posts/games-of-summer-2024" rel="alternate" type="text/html" title="Games of Summer 2024" /><published>2024-09-22T00:00:00+00:00</published><updated>2024-09-22T00:00:00+00:00</updated><id>https://mthadley.com/posts/games-of-summer-2024</id><content type="html" xml:base="https://mthadley.com/posts/games-of-summer-2024"><![CDATA[<p>Really, this is more the “Games of 2024” <em>so far</em>, but I’m writing this at the
end of summer, which seems like a good time to look back. All of these were
played on my trusty <a href="https://www.steamdeck.com/en/deck">Steam Deck</a>, which is still proving to be my favorite way
to play games; handheld, connected to a TV, or with a mouse and keyboard via a
USB-C dock.</p>

<p>There were some other miscellaneous games I played. Some of them were huge and
live large in the gaming consciousness, such as Helldivers 2, but I don’t have
much to add over what has already been said.</p>

<h2 id="metal-gear-solid-2-sons-of-liberty" style="background-image: linear-gradient(39deg, var(--light-blue), var(--blue), var(--pink));"><a href="https://en.wikipedia.org/wiki/Metal_Gear_Solid_2:_Sons_of_Liberty">Metal Gear Solid 2: Sons of Liberty</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/mgs2-480-ac1ea09e1.webp 480w, /assets/images/games-of-summer-2024/mgs2-768-ac1ea09e1.webp 768w, /assets/images/games-of-summer-2024/mgs2-1067-ac1ea09e1.webp 1067w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/mgs2-480-c567f2524.jpg 480w, /assets/images/games-of-summer-2024/mgs2-768-c567f2524.jpg 768w, /assets/images/games-of-summer-2024/mgs2-1067-c567f2524.jpg 1067w" type="image/jpeg" /><img src="/assets/images/games-of-summer-2024/mgs2-480-c567f2524.jpg" alt="Solid Snake sneaking down a cargo ship hallway." width="1067" height="800" /></picture>

<p>You might think I was inspired by the announcement of the upcoming <a href="https://www.konami.com/mg/mgs3r/us/en/">Snake Eater</a>
remake to play <a href="https://en.wikipedia.org/wiki/Metal_Gear_Solid_2:_Sons_of_Liberty">Sons of Liberty</a>, but it really wasn’t anything in particular.
Just some vague association with the early 2000’s, which I often have, and which
triggers me to play a game from the period that I never had the chance to.</p>

<p>This game and its story is as wacky as you’d expect, but go in not taking it
seriously, and it still holds up. The graphics are awesome if you compare it to
its contemporaries. Especially with emulation and 3x scaling enabled; I played
this via Playstation 2 emulation as I wanted to play an original version and not
a later PC port. Lots of strangely-specific attention to detail, too. Try
shooting Seagulls and observe how your in-game allies react.</p>

<p>I find the fixed camera style endearing, but the archaic controls less so.
Certain actions depend on the PS2’s lesser remembered <a href="https://en.wikipedia.org/wiki/DualShock#DualShock_2">pressure sensitive
buttons</a>, which I couldn’t figure out how to make compatible with the Steam
Deck’s controller.</p>

<p>I remember criticism from the time about the length of cut-scenes, and boy were
they right. You’ll go from watching a 10 minute scene, gain back control to
cross a short hallway, and immediately start another 10 minute cut-scene. It’s
honestly hilarious, and I was glad emulation gave me the ability to pause and
save states at any time for breaks.</p>

<!-- prettier-ignore-start -->
<!-- prettier-ignore-end -->

<h2 id="ufo-50" style="background-image: linear-gradient(44deg, var(--light-blue), var(--pink), var(--blue));"><a href="https://store.steampowered.com/app/1147860/UFO_50/">UFO 50</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/ufo_50-600-c4910ddcf.webp 600w, /assets/images/games-of-summer-2024/ufo_50-900-c4910ddcf.webp 900w, /assets/images/games-of-summer-2024/ufo_50-1280-c4910ddcf.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/ufo_50-600-ae0861f2e.jpg 600w, /assets/images/games-of-summer-2024/ufo_50-900-ae0861f2e.jpg 900w, /assets/images/games-of-summer-2024/ufo_50-1280-ae0861f2e.jpg 1280w" type="image/jpeg" /><img src="/assets/images/games-of-summer-2024/ufo_50-600-ae0861f2e.jpg" alt="The main screen of Avianos, a retro strategy game in UFO 50." width="1280" height="800" /></picture>

<p>This is the most recent game I’ve started playing and <a href="https://store.steampowered.com/app/1147860/UFO_50/">UFO 50</a> is really <em>50</em>
games instead of just one, so I still have more to explore. It’s a collection of
retro-style games packaged as if they were actually released 40 years ago on a
game console that never existed.</p>

<p>From the subset of the 50 I’ve tried, there’s already an amazing amount of
variety: action, racing, puzzle, strategy, and even lots of same-screen
multiplayer support. Each game seems to be a remix of other well-known games,
generally with a little twist thrown in. All feel very creative.</p>

<p>My favorite so far is a strategy game that somehow splits the difference between
advance wars, Heroes of Might &amp; Magic, and one of those <a href="https://en.wikipedia.org/wiki/Auto_battler">auto battler</a> games.
From UFO 50’s main screen you “dust-off” each game for the first time, and I
instantly lost an hour on this one, and I still have 40 more to go!</p>

<h2 id="selaco" style="background-image: linear-gradient(58deg, var(--pink), var(--light-blue), var(--blue));"><a href="https://store.steampowered.com/app/1592280/Selaco/">Selaco</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/selaco-600-2072b60c7.webp 600w, /assets/images/games-of-summer-2024/selaco-900-2072b60c7.webp 900w, /assets/images/games-of-summer-2024/selaco-1280-2072b60c7.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/selaco-600-ddbf821d3.jpg 600w, /assets/images/games-of-summer-2024/selaco-900-ddbf821d3.jpg 900w, /assets/images/games-of-summer-2024/selaco-1280-ddbf821d3.jpg 1280w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/selaco-600-ddbf821d3.jpg" alt="Shooting down one of Selaco's many dark industrial structures." width="1280" height="800" /></picture>

<p>A product of the <a href="https://en.wiktionary.org/wiki/boomer_shooter">boomer shooter</a> craze of the past few years, but maybe the
best of all of them. I’m a huge Doom fan (old and new) so this one was right up
my alley.</p>

<p>Selaco’s graphics are in that pixelated style and look fantastic. The detail of
the environments are impressive too, with tons of interactive and physical
doodads. All of this is made more impressive by the fact the game is built on
<em><a href="https://zdoom.org/downloads">GZDoom</a></em>. Absolutely wild.</p>

<p>It’s no surprise the gameplay is somewhat similar to Doom: it’s a shooter after
all. However, it’s really more of a mix between Fear, specifically the combat,
and Bioshock, taking elements like audio logs, vending machines, and environment
exploration.</p>

<p>Definitely worth a look if you are a single-player shooter fan like me.</p>

<h2 id="deadlock" style="background-image: linear-gradient(59deg, var(--blue), var(--pink), var(--light-blue));"><a href="https://store.steampowered.com/app/1422450/Deadlock/">Deadlock</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/deadlock-600-187fd9dfc.webp 600w, /assets/images/games-of-summer-2024/deadlock-900-187fd9dfc.webp 900w, /assets/images/games-of-summer-2024/deadlock-1280-187fd9dfc.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/deadlock-600-8c6b5fb88.jpg 600w, /assets/images/games-of-summer-2024/deadlock-900-8c6b5fb88.jpg 900w, /assets/images/games-of-summer-2024/deadlock-1280-8c6b5fb88.jpg 1280w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/deadlock-600-8c6b5fb88.jpg" alt="Playing as some robot character in Deadlock's hero sandbox mode." width="1280" height="800" /></picture>

<p>These days I don’t play a lot of competitive multiplayer games. I used to, but
no longer enjoy them as much in this phase of my life. Maybe it’s not just me
but that the games have changed in a way that’s less appealing. I’m still
deciding.</p>

<p>In any case, this is relevant because <a href="https://store.steampowered.com/app/1422450/Deadlock/">Deadlock</a> <em>is</em> a competitive multiplayer
game, <em>and</em> it’s a <a href="https://en.wikipedia.org/wiki/Multiplayer_online_battle_arena">MOBA</a>, a genre I’ve mostly stayed away from. On the surface
it doesn’t seem like one I’d go for. But it has a few things in its favor: it’s
made by Valve, a developer I <em>adore</em>, and it’s a shooter instead of a top-down,
which I like more. So I had to try it.</p>

<p>With the caveat that the game is still early in development, I see a lot of
promise. The gameplay feels tight and interesting. A lot of the MOBA elements
are at least new to me, and I like all of the guidance they’ve already built
into the game for newbies like myself, like suggested ability upgrades,
community curated item templates, and recommended first characters.</p>

<p>Unfortunately, I still see many of the things I don’t like. Sweaty matchmaking
with a core game loop of farming souls (or whatever they are called) that feels
exhausting. I’m still excited to see how it evolves and will probably just play
along as casually as possible.</p>

<h2 id="alien-isolation" style="background-image: linear-gradient(50deg, var(--light-blue), var(--pink), var(--blue));"><a href="https://store.steampowered.com/app/214490/Alien_Isolation/">Alien: Isolation</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/alien-600-d6c721f77.webp 600w, /assets/images/games-of-summer-2024/alien-900-d6c721f77.webp 900w, /assets/images/games-of-summer-2024/alien-1280-d6c721f77.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/alien-600-a29890c8d.jpg 600w, /assets/images/games-of-summer-2024/alien-900-a29890c8d.jpg 900w, /assets/images/games-of-summer-2024/alien-1280-a29890c8d.jpg 1280w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/alien-600-a29890c8d.jpg" alt="Viewing a chart in the medical bay while avoiding the titular alien." width="1280" height="800" /></picture>

<p>Yet another type of game I normally don’t go for: horror. But <em><a href="https://store.steampowered.com/app/214490/Alien_Isolation/">Alien:
Isolation</a></em> had been in my back catalog for <em>many</em> years, and I finally found
time for it.</p>

<p>It was mostly the game’s attention to detail in its recreation of the Alien
movies that attracted me. And indeed, they did a fantastic job here. I actually
watched the original <a href="https://www.imdb.com/title/tt0078748/">1979 Alien movie</a> not long after finishing the game, and
was amazed by how accurately the game developers replicated the details.</p>

<p>I did enjoy the elements of exploration in the game, but the horror aspects did
nothing for me, and really had to slog my way through those parts. As a
full-grown adult, I still have to admit those facehuggers gave me more than a
few jumps.</p>

<h2 id="dread-delusion" style="background-image: linear-gradient(59deg, var(--blue), var(--light-blue), var(--pink));"><a href="https://store.steampowered.com/app/1574240/Dread_Delusion/">Dread Delusion</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/dread_delusion-600-417b54078.webp 600w, /assets/images/games-of-summer-2024/dread_delusion-900-417b54078.webp 900w, /assets/images/games-of-summer-2024/dread_delusion-1280-417b54078.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/dread_delusion-600-6968e841a.jpg 600w, /assets/images/games-of-summer-2024/dread_delusion-900-6968e841a.jpg 900w, /assets/images/games-of-summer-2024/dread_delusion-1280-6968e841a.jpg 1280w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/dread_delusion-600-6968e841a.jpg" alt="Looking out over the floating plains under a blood-red moon." width="1280" height="800" /></picture>

<p>An indie game that had a lot of the right words said about it to get it on my
radar. Those retro graphics with the wibbly-wobbly PS1 textures. An unusual
setting and environment that felt like <a href="https://en.wikipedia.org/wiki/The_Elder_Scrolls_III:_Morrowind">Morrowind</a> on acid. Plus, a strong focus
on exploration and just a certain sort of <em>vibe</em>.</p>

<p>At least for the first few hours, this all worked pretty well for me. I really
do like how progression is strongly linked to exploration, encouraging you to
investigate every nook and cranny of the game world. Sometimes you’ll find
ability points, or gold, or a terrifying monstrosity that turns out just wants
to have a conversation.</p>

<p>Unfortunately, I fell off this one. While the game wears its Morrowind
influences on its sleeve, too much seeped into its combat, which is not
particularly fun. Perhaps the game was also <em>too</em> weird for me, and so the story
never pulled me in.</p>

<h2 id="immortality" style="background-image: linear-gradient(48deg, var(--blue), var(--light-blue), var(--pink));"><a href="https://store.steampowered.com/app/1350200/IMMORTALITY/">Immortality</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/immortality-480-71481d782.webp 480w, /assets/images/games-of-summer-2024/immortality-768-71481d782.webp 768w, /assets/images/games-of-summer-2024/immortality-1067-71481d782.webp 1067w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/immortality-480-953f71177.jpg 480w, /assets/images/games-of-summer-2024/immortality-768-953f71177.jpg 768w, /assets/images/games-of-summer-2024/immortality-1067-953f71177.jpg 1067w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/immortality-480-953f71177.jpg" alt="The title screen of Immortality." width="1067" height="800" /></picture>

<p>Easily the most “different” game I’ve played this year, or really in a <em>long</em>
time. It’s less of a game and more of an interactive, non-linear movie with
surreal and supernatural elements.</p>

<p>Given that as the player you are mostly watching rather than “playing”, meaning
there’s no real fun from holding the controller, the game had the surprising
effect of being enjoyable by my partner as well. It was fun to attempt to
unravel the story together, and benefited from having more both of our heads put
together, as we each noticed different things.</p>

<p>There’s a moment where a certain “mechanic” is finally discovered that is really
the best part of the game. It reminded me of a similar feeling I got from <a href="https://store.steampowered.com/app/210970/The_Witness/">The
Witness</a>.</p>

<p>Definitely check out <a href="https://store.steampowered.com/app/1350200/IMMORTALITY/">Immortality</a> if you’re looking for something different.
Note that the game does have mature elements, so not one for the kids.</p>

<h2 id="journey" style="background-image: linear-gradient(50deg, var(--blue), var(--pink), var(--light-blue));"><a href="https://store.steampowered.com/app/638230/Journey/">Journey</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/journey-600-39c39d86e.webp 600w, /assets/images/games-of-summer-2024/journey-900-39c39d86e.webp 900w, /assets/images/games-of-summer-2024/journey-1280-39c39d86e.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/journey-600-7c9488ef4.jpg 600w, /assets/images/games-of-summer-2024/journey-900-7c9488ef4.jpg 900w, /assets/images/games-of-summer-2024/journey-1280-7c9488ef4.jpg 1280w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/journey-600-7c9488ef4.jpg" alt="On top of a ruin in the middle of a desert's rolling dunes." width="1280" height="800" /></picture>

<p>Yet another game that made its way onto my backlog after hearing so much about
it over a decade ago. I don’t have too much to add so I’ll just keep it short
and repeat the obvious parts: A beautiful game, short, sweet, and surprising
multiplayer elements.</p>

<h2 id="ori-and-the-blind-forest" style="background-image: linear-gradient(45deg, var(--pink), var(--blue), var(--light-blue));"><a href="https://store.steampowered.com/app/261570/Ori_and_the_Blind_Forest/">Ori and the Blind Forest</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/ori-600-1b0b44cab.webp 600w, /assets/images/games-of-summer-2024/ori-900-1b0b44cab.webp 900w, /assets/images/games-of-summer-2024/ori-1280-1b0b44cab.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/ori-600-c4aed2c36.jpg 600w, /assets/images/games-of-summer-2024/ori-900-c4aed2c36.jpg 900w, /assets/images/games-of-summer-2024/ori-1280-c4aed2c36.jpg 1280w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/ori-600-c4aed2c36.jpg" alt="Inside of a bubbling volcano near the end of Ori's storyline." width="1280" height="800" /></picture>

<p>Continuing the theme of backlog clearing (I really made a dent this year!), Ori
was one I knew I would eventually get around to. I love the <a href="https://en.wikipedia.org/wiki/Metroidvania">Metroidvania</a>
genre, and while Ori stuck to the core of those games pretty tightly, I was
still pleased to see it try some new ideas.</p>

<p>The game has an unusual resource management system tied to saving. You are given
a limited number of saves in the form of energy, requiring you to optimize the
best places to do so. You may want to save after each difficult jump or fight,
but do so too often and you’ll quickly find yourself needing to repeat longer
stretches of later parts of the area. Important as the game is no push-over.</p>

<p>Otherwise, the game is beautiful, the music ethereal, and the whole thing has
the vibe of a big animated movie. Worth a look given how often this game is sold
for only $5.</p>

<!-- prettier-ignore-start -->
<!-- prettier-ignore-end -->

<h2 id="system-shock" style="background-image: linear-gradient(33deg, var(--pink), var(--light-blue), var(--blue));"><a href="https://store.steampowered.com/app/482400/System_Shock/">System Shock</a></h2>

<picture><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/system_shock-600-cdd45dfef.webp 600w, /assets/images/games-of-summer-2024/system_shock-900-cdd45dfef.webp 900w, /assets/images/games-of-summer-2024/system_shock-1280-cdd45dfef.webp 1280w" type="image/webp" /><source sizes="(min-width: 650px) 618px, calc(100vw - 32px)" srcset="/assets/images/games-of-summer-2024/system_shock-600-790c521e0.jpg 600w, /assets/images/games-of-summer-2024/system_shock-900-790c521e0.jpg 900w, /assets/images/games-of-summer-2024/system_shock-1280-790c521e0.jpg 1280w" type="image/jpeg" /><img loading="lazy" src="/assets/images/games-of-summer-2024/system_shock-600-790c521e0.jpg" alt="Viewing a room code on the wall while holding a shotgun." width="1280" height="800" /></picture>

<p>I have a tiny bit of nostalgia for the original System Shock, having played it
as a kid. I think it was actually a demo or shareware version, not the actual
full game. I remember being very confused, barely making it out of the first
several rooms, but still being mesmerized (really, any game did that to me back
then).</p>

<p>So I was excited to check out the remake, especially having played some of the
other remasters from <a href="https://www.nightdivestudios.com">Nightdive Studios</a>. But it’s clear they went much farther
with this game, where they essentially built a brand new game around the bones
of the original.</p>

<p>It runs great and the controls are modern, even on the Steam Deck. The cyberpunk
atmosphere comes through in a way that was impossible for the original game to
ever convey. If you are a fan of the Bioshock series like I am, you owe it to
yourself to see where the series started from.</p>

<h2 id="wrapping-up" style="background-image: linear-gradient(49deg, var(--blue), var(--light-blue), var(--pink));">Wrapping Up</h2>

<p>That’s all the games for now. I still have more games that I’ve been meaning to
play, so you may find another roundup like this one in another couple of months.
I may also do a few one-off posts about specific games like I did for <a href="/posts/myst">Myst</a>,
which reminds me that the remake for <a href="https://store.steampowered.com/app/1712350/Riven/">Riven</a> came out in the mean time, so maybe
that’ll be next. Until then!</p>

<!-- prettier-ignore-start -->
<!-- prettier-ignore-end -->]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[What I've been playing over the past couple of months.]]></summary></entry><entry><title type="html">Undefined in TypeScript</title><link href="https://mthadley.com/posts/undefined-in-typescript" rel="alternate" type="text/html" title="Undefined in TypeScript" /><published>2024-07-27T00:00:00+00:00</published><updated>2024-07-27T00:00:00+00:00</updated><id>https://mthadley.com/posts/undefined-in-typescript</id><content type="html" xml:base="https://mthadley.com/posts/undefined-in-typescript"><![CDATA[<p>Every programming language has the concept of “no value”. Many follow the
example of C and its <code class="language-plaintext highlighter-rouge">NULL</code>; Ruby has <code class="language-plaintext highlighter-rouge">nil</code>, Python <code class="language-plaintext highlighter-rouge">None</code>. More recent and
functional languages like Rust or Elm use a union type, like <code class="language-plaintext highlighter-rouge">Option&lt;T&gt;</code> or
<code class="language-plaintext highlighter-rouge">Maybe a</code>, with a union member representing “nothing”, but let’s ignore those
for now.</p>

<p>JavaScript is different in that it has <em>two</em> primitive types to represent the
absence of a value,
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null"><code class="language-plaintext highlighter-rouge">null</code></a>
and
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined"><code class="language-plaintext highlighter-rouge">undefined</code></a>.
I’m personally not aware of another language that does this.</p>

<h2 id="undefined" style="background-image: linear-gradient(43deg, var(--pink), var(--light-blue), var(--blue));">Undefined</h2>

<p>Most of us programmers have written at least some JavaScript by now, and
probably first encounter <code class="language-plaintext highlighter-rouge">undefined</code> with the infamous “undefined is not a
function”. This happens because JavaScript runtimes use <code class="language-plaintext highlighter-rouge">undefined</code> as the
“uninitialized” value; either a variable without an assignment, or a
non-existent field of an object. The latter often being a typo or type-mismatch.</p>

<p><code class="language-plaintext highlighter-rouge">undefined</code> is built into the semantics of the language. A bare <code class="language-plaintext highlighter-rouge">return</code>
statement with no value causes a function to return <code class="language-plaintext highlighter-rouge">undefined</code>. A function with
no <code class="language-plaintext highlighter-rouge">return</code> at all also returns <code class="language-plaintext highlighter-rouge">undefined</code>. A function with an optional
parameter has its value initialized as <code class="language-plaintext highlighter-rouge">undefined</code> when no argument is given.</p>

<h2 id="null" style="background-image: linear-gradient(30deg, var(--pink), var(--light-blue), var(--blue));">Null</h2>

<p>On the other hand, <code class="language-plaintext highlighter-rouge">null</code> does not “naturally” occur through the usage of
language constructs. A programmer needs to explicitly initialize, pass, or
return a value of <code class="language-plaintext highlighter-rouge">null</code>. It’s much more intentional, and meant to represent the
explicit absence of data.</p>

<p>This is in contrast to <code class="language-plaintext highlighter-rouge">undefined</code>, which is often more like “Oops, you are
trying to access something that doesn’t exist” by mistake.</p>

<h2 id="inconsistency" style="background-image: linear-gradient(32deg, var(--pink), var(--blue), var(--light-blue));">Inconsistency</h2>

<p>Consider methods meant to retrieve data. Your “fetchers” and “getters” of the
world. From my description of the <code class="language-plaintext highlighter-rouge">undefined</code> and <code class="language-plaintext highlighter-rouge">null</code>, you might expect that
these all prefer <code class="language-plaintext highlighter-rouge">null</code>. Here are some common browser APIs:</p>

<table>
  <thead>
    <tr>
      <th>API</th>
      <th><code class="language-plaintext highlighter-rouge">null</code></th>
      <th><code class="language-plaintext highlighter-rouge">undefined</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find#return_value"><code class="language-plaintext highlighter-rouge">Array.prototype.find()</code></a></td>
      <td> </td>
      <td>✓</td>
    </tr>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/API/Cache/match#return_value"><code class="language-plaintext highlighter-rouge">Cache.match()</code></a></td>
      <td> </td>
      <td>✓</td>
    </tr>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#return_value"><code class="language-plaintext highlighter-rouge">Document.querySelector()</code></a></td>
      <td>✓</td>
      <td> </td>
    </tr>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData/get#return_value"><code class="language-plaintext highlighter-rouge">FormData.get()</code></a></td>
      <td>✓</td>
      <td> </td>
    </tr>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/API/Headers/get#return_value"><code class="language-plaintext highlighter-rouge">Headers.get()</code></a></td>
      <td>✓</td>
      <td> </td>
    </tr>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get#return_value"><code class="language-plaintext highlighter-rouge">Map.get()</code></a></td>
      <td> </td>
      <td>✓</td>
    </tr>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem#return_value"><code class="language-plaintext highlighter-rouge">Storage.getItem()</code></a></td>
      <td>✓</td>
      <td> </td>
    </tr>
    <tr>
      <td><a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/get#return_value"><code class="language-plaintext highlighter-rouge">URLSearchParams.get()</code></a></td>
      <td>✓</td>
      <td> </td>
    </tr>
  </tbody>
</table>

<p>Yeah, they aren’t consistent at all. My random sampling leans towards <code class="language-plaintext highlighter-rouge">null</code>,
but barely so. Maybe <code class="language-plaintext highlighter-rouge">Map.get()</code> is trying to be consistent with <code class="language-plaintext highlighter-rouge">[]</code> access of
an object when the key doesn’t exist, but then why does <code class="language-plaintext highlighter-rouge">FormData</code> return <code class="language-plaintext highlighter-rouge">null</code>
for the same case?</p>

<h2 id="typescript" style="background-image: linear-gradient(32deg, var(--blue), var(--pink), var(--light-blue));">TypeScript</h2>

<p>When you start writing TypeScript, you run headlong into this inconsistency. If
you don’t give it much thought, you end up with long union types everywhere like
<code class="language-plaintext highlighter-rouge">string | null | undefined</code> as the results of various operations get smashed
together.</p>

<p>You might react to this with a “pick one” policy, with most folks picking
<code class="language-plaintext highlighter-rouge">undefined</code>, since it shows up naturally from basic language semantics. Just
don’t use <code class="language-plaintext highlighter-rouge">null</code> whenever possible. In some ways, this mirrors the other
languages like Ruby, with <code class="language-plaintext highlighter-rouge">undefined</code> playing the role of <code class="language-plaintext highlighter-rouge">nil</code>.</p>

<p>However, I’ve seen this approach backfire. I’ve used a popular ORM where a field
with a value of <code class="language-plaintext highlighter-rouge">undefined</code> meant “don’t update this field in the resulting
SQL”, while <code class="language-plaintext highlighter-rouge">null</code> meant “set the underlying column value to SQL <code class="language-plaintext highlighter-rouge">NULL</code>”. This
led to subtle bugs where data was not being cleared, since the rest of the
codebase was trying to prefer <code class="language-plaintext highlighter-rouge">undefined</code>.</p>

<h2 id="why-not-both" style="background-image: linear-gradient(58deg, var(--light-blue), var(--blue), var(--pink));">Why Not Both?</h2>

<p>Ruby is a language I love and so in general I enjoy the simplicity of a single
null-like value, <code class="language-plaintext highlighter-rouge">nil</code>. But surprisingly there are cases where using <code class="language-plaintext highlighter-rouge">nil</code> ends
up being lossy.</p>

<p>If you define a method that takes an optional parameter, and the caller passes
no value, you’ll generally assign it the default of <code class="language-plaintext highlighter-rouge">nil</code>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">options</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
  <span class="c1"># Do your foo thing</span>
<span class="k">end</span>
</code></pre></div></div>

<p>But what if the caller <em>explicitly</em> passes the optional parameter as <code class="language-plaintext highlighter-rouge">nil</code>? It
may not matter for your particular method, but certainly sometimes it does. See
<a href="https://github.com/rails/rails/blob/9826d743aec09b2f34ab0c20acf7270a1f081187/activejob/lib/active_job/exceptions.rb#L158">how Rails</a>
uses object singletons to detect this.</p>

<p>So maybe the concept of <code class="language-plaintext highlighter-rouge">undefined</code> has its place, and we should use it
alongside <code class="language-plaintext highlighter-rouge">null</code> judiciously.</p>

<!-- Prettier tried to wrap these, which isn't compatible with Jekyll's Markdown parser -->
<!-- prettier-ignore-start -->
<!-- prettier-ignore-end -->]]></content><author><name>Michael Hadley</name></author><summary type="html"><![CDATA[Often misused, but maybe still useful?]]></summary></entry></feed>