<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Caffeinspiration</title>
    <link>https://alexanderell.is/</link>
    <description>Recent content on Caffeinspiration</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Sun, 16 Nov 2025 16:27:10 -0500</lastBuildDate><atom:link href="https://alexanderell.is/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Timeline and Early Days</title>
      <link>https://alexanderell.is/posts/heart/timeline-and-early-days/</link>
      <pubDate>Sun, 16 Nov 2025 16:27:10 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/heart/timeline-and-early-days/</guid>
      <description>&lt;h1 id=&#34;timeline-and-discovery-the-early-days&#34;&gt;Timeline and discovery: the early days&lt;/h1&gt;
&lt;p&gt;The simple timeline of my journey sounds deceptively easy at first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I found out about the issue after an echocardiogram on January 23rd, 2025.&lt;/li&gt;
&lt;li&gt;I had open heart surgery on Tuesday, June 24th&lt;/li&gt;
&lt;li&gt;I was sent home from the hospital three days later on Friday, June 27th&lt;/li&gt;
&lt;li&gt;I hit the 12-week mark on Tuesday, September 16th&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s now Sunday, November 16th&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt; 
&lt;p&gt;Just a quick ten months in which I had to process, learn, understand, talk to experts, learn some more, imagine worst
case scenarios, cope, walk, constantly be aware of my heart, try not to think about it, try to carry on, get more scans,
get second, third, and even fourth opinions, become an expert at navigating these medical appointments, make difficult
decisions, make the call and pick a date, get a list of all of the financial accounts and their passwords just in case,
get covid, get better, invite family to town, take medical leave, show up at the hospital, put one foot in front of the
other, take a quick nap, wake up, spend three grueling days at the hospital, make it home, start recovery, build up my
walking again, not pick anything more than ten pounds up for three months, get better, starting picking things up again,
start jogging again, and return closer and closer to a normal life.&lt;/p&gt;
&lt;p&gt;Not bad!&lt;/p&gt;
&lt;p&gt;Naturally, the full timeline is much more complex. Looking back, there has definitely been some time dilation going on
here in my memory, between the rawness and the flurry of activity pre-surgery, the overwhelming intensity of the 3-5
days of the surgery and hospital stay, and the floating repetitiveness of my recovery months. Really, it&amp;rsquo;s as if the
last ten months are separated into three even chunks of time in my memory, which above all else really underlines just
how intense the few days around the surgery really were:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;timeline.png&#34; alt=&#34;Drawing of a timeline with those 3 even chunks&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;But for now, let&amp;rsquo;s return to the start.&lt;/p&gt;
&lt;p&gt;The longer story really starts much earlier. My family has a complex medical history, particularly around cardiac
health, with issues on either side. Most immediately relevant to my own experience is my father&amp;rsquo;s history, where he had
to have a mitral valve repair, then a repair of a repair. He ended up passing away from a presumed cardiac incident a
little over two years ago.&lt;/p&gt;
&lt;p&gt;With that in mind, my siblings and I all share these genes, and all of us got echocardiograms, where a technician
measures the various sizes, diameters, blood flows, and efficiencies of your heart, valves, and vessels. Having one done
when you&amp;rsquo;re younger can provide a good baseline to compare against as you get older, allowing you to measure things like
changes in sizes or valve efficiency over time.&lt;/p&gt;
&lt;p&gt;That was the mindset I had when I scheduled an echocardiogram in January; let&amp;rsquo;s measure so we can have the data points
to compare against if health issues start to show up in my 50s or 60s. Calm as a cucumber, I went in for the echo, and,
knowing the smallest bit of heart anatomy from a random heart physics class I took in college, I even joked with the
tech about one of my valves having three leaflets, just as it should. I had an appointment with my cardiologist
immediately after the echo, and I was expecting a short and sweet &amp;ldquo;all&amp;rsquo;s well, see you later&amp;rdquo; that I had come to expect
from most of my visits.&lt;/p&gt;
&lt;p&gt;In retrospect, the first sign that something wasn&amp;rsquo;t right might have been that they came in and let me know they decided
they wanted to get an EKG as well. I remember it was a chilly 20 degree day that I had gotten bundled up for to walk to
my appointment, and I had to roll back my perfectly tucked socks and wool long underwear so they could put a lead on my
leg. The tech even apologized for disrupting my perfect layering. It&amp;rsquo;s funny what little things stick out in your
memory.&lt;/p&gt;
&lt;p&gt;When the cardiologist came in and shared the results of my echo, the main things I remember clearly are the words &amp;quot;
aortic aneurysm&amp;quot;, &amp;ldquo;likely need surgery to fix&amp;rdquo;, and &amp;ldquo;refer you to a cardiac surgeon&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&amp;hellip;!&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve ever been shocked with bad news, you probably know the sensation of immediately being completely overwhelmed.
It was almost as if everything else shut down while my brain worked to re-process those words, each of which I knew, but
also knew they didn&amp;rsquo;t apply to me. Surely it wasn&amp;rsquo;t little old me at 32 and relatively good health — this was not how
these appointments were supposed to go.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like to see what it roughly felt like, I have an easy four step process to try it out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, turn on an annoyingly high pitched noise to set the mood (tinnitus-enjoyers can skip this step)&lt;/li&gt;
&lt;li&gt;Second, plug your ears so the only thing you can hear is your breathing and the beating of your own heart&lt;/li&gt;
&lt;li&gt;Third, close your eyes, because you couldn&amp;rsquo;t&lt;/li&gt;
&lt;li&gt;Finally, repeat in a whisper to yourself: &amp;ldquo;you need heart surgery, you might die, and your life will never be the same&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Fun!&lt;/p&gt;
&lt;p&gt;I motioned from my neck down my sternum and asked if it would be &lt;em&gt;heart surgery&lt;/em&gt; heart surgery, and my cardiologist said
it would be up to the surgeon, but likely yes. They mentioned a follow-up CT scan to get even better images and
measurements, and they recommended stopping any strenuous physical exercise, as spikes in blood pressure can worsen the
aneurysm and lead to tears, which would be very, very bad.&lt;/p&gt;
&lt;p&gt;Physical exercise could be bad, you say? Funny you should say that, because three weeks before, I had been doing very
strenuous physical exercise in Joshua Tree, rock climbing and scrambling with no cellphone service, even occasionally
going out on solo morning scrambles deep in the park on random, infrequently visited areas. There easily could have been
a parallel timeline where something happened during one of those scrambles, and that would have been it for me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;jtree.png&#34; alt=&#34;Drawing of Joshua Tree rocks I scrambled with sign post: &amp;amp;ldquo;here lies Alex (somewhere!)&amp;amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;My Joshua Tree alternate timeline resting place&lt;/em&gt;  
&lt;/center&gt;
&lt;br&gt;
&lt;p&gt;I walked home from that appointment in an absolute daze. I remember that my main thought was how I was going to break
this news to my wife. We had been joking and silly before I left, and coming back with this kind of news was guaranteed
to cloud the rest of the day. There was a fleeting impulse to not tell her at all — let&amp;rsquo;s just keep this terrible news
to myself and not ruin anyone else&amp;rsquo;s sunny January day. I floated past the rest of the 25 minute walk, with each foot
bringing me a little closer to the difficult thing I&amp;rsquo;d inevitably have to soon do.&lt;/p&gt;
&lt;p&gt;On the way back, I experienced this odd sensation of forcing myself to put one foot in front of another, making myself
move forward even though I was scared, uncertain, and not looking forward to it. I knew what I would have to do — return
to my apartment and share the news with my wife — and I just had to go through the motion as best I could. This would
end up being a recurring theme throughout my entire journey; knowing what I have to do, I just have to go step by step.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;map.png&#34; alt=&#34;Map of my walking path to the hospital (fun on the way there) and back (not so fun)&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;Biblically accurate map of Somerville with my walk to the appointment and back&lt;/em&gt;
&lt;/center&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;Naturally, I did tell my wife, and naturally, it did fuck up her day too.&lt;/p&gt;
&lt;p&gt;Once you know something like that, you can&amp;rsquo;t go unlearn it. The pre-aneurysm times were over, even though we didn&amp;rsquo;t even
know at the time how lucky we were to be in the pre-aneurysm times. Life was immediately divided into a &amp;ldquo;before&amp;rdquo; and
&amp;ldquo;now&amp;rdquo;, with the new chapter starting with a bang. Funny enough, it was only the new knowledge that was causing this
split. It was likely that nothing had physically changed from the day before, but now we knew about it, this new cursed
fact infecting every part of our thoughts.&lt;/p&gt;
&lt;p&gt;Along with the knowledge, there were so many questions. What did this actually mean? Would I really need surgery? What
if it isn&amp;rsquo;t really a problem and we can just wait it out? Why did this happen to me? What happens next? As you can tell,
the story goes well, but at the time, we were very far from knowing it would.&lt;/p&gt;
&lt;p&gt;This is how the story began.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;(&lt;a href=&#34;../&#34;&gt;Link back to heart topics&lt;/a&gt;)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>On having heart surgery at 32</title>
      <link>https://alexanderell.is/posts/heart/</link>
      <pubDate>Sun, 09 Nov 2025 13:00:16 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/heart/</guid>
      <description>&lt;p&gt;&lt;em&gt;Author&amp;rsquo;s note: this is a departure from topics that I usually write about. It&amp;rsquo;s not related to tech, learning, or
anything I&amp;rsquo;ve written about before; it&amp;rsquo;s my personal experience going through heart surgery at 32.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Reader, be warned that there are personal and real details. There won&amp;rsquo;t be any gory images, but I won&amp;rsquo;t be sanitizing
the experience or pretending it was just sunshine and rainbows and not incredibly difficult. I&amp;rsquo;m writing this for
myself, and though it might be difficult at parts, you&amp;rsquo;ll find it to be true.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is &lt;em&gt;my&lt;/em&gt; website, after all.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;With that said, enjoy!&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In June 2025, at 32 years of age, I had to have open heart surgery. I had an aortic aneurysm, which means that my aorta
(the big tube that transports blood pumped from your heart to the rest of your body) was wider than it should have been
in one critical section. This can be an important problem to fix, because it can grow larger and tear, at which point
you have a very, very big problem.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m incredibly fortunate that we caught it, met some wonderful doctors and surgeons, and were able to have it fixed.
Everything went extremely well, and my recovery has been tremendous. I&amp;rsquo;m fully fixed, and life is returning to normal.&lt;/p&gt;
&lt;p&gt;This was one of the most difficult things I&amp;rsquo;ve ever gone through in my life, and as you can imagine, I&amp;rsquo;ve gone through
the entire spectrum of emotions, from deep grief from facing death to deep gratitude for having found it in the first
place and joy at having had everything go as well as it has.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve found that writing is one of the best methods I have for processing things, and in the months since my surgery,
I&amp;rsquo;ve been filled with a desire to write about my experience. I&amp;rsquo;m largely doing this for myself, but I&amp;rsquo;ve found joy
sharing my experience with others, especially if I can spread some humor at the same time (one of my key coping
mechanisms!). On top of that, if this helps at least one other person dealing with a similar situation, then that&amp;rsquo;ll
just be the cherry on top.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m likely going to approach it by writing little vignettes about various parts of the experience. There may some day be
some chronological order to it, but for now, I&amp;rsquo;m just going to write about whatever I feel like for that day. I&amp;rsquo;ll be
adding them here over time so they don&amp;rsquo;t take up my entire landing page post list, since I&amp;rsquo;ll likely be continuing with
regular programming as well. &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;cathy&#34;&gt;Cathy&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s start it off with a bang, remembering my dear friend &lt;a href=&#34;./catheter&#34;&gt;Cathy the catheter&lt;/a&gt; — I&amp;rsquo;ll never forget the time
we spent close together, for better or for worse.&lt;/p&gt;
&lt;p&gt;I warned you about it being personal!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;timeline-and-early-days&#34;&gt;Timeline and Early Days&lt;/h3&gt;
&lt;p&gt;Next up is a nice, relaxing echocardiogram and cardiologist appointment on a chilly January day:
&lt;a href=&#34;./timeline-and-early-days&#34;&gt;Timeline and Early Days&lt;/a&gt;&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;This is a good time to mention that I&amp;rsquo;m not a medical doctor, and obviously nothing written here is medical
advice.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Though maybe I&amp;rsquo;ll start having this site be more of my personal writing in general as a place to share it.
Especially these days, I don&amp;rsquo;t care about it helping with my career hustling.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Cathy: my dear, helpful catheter</title>
      <link>https://alexanderell.is/posts/heart/catheter/</link>
      <pubDate>Sun, 09 Nov 2025 13:00:11 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/heart/catheter/</guid>
      <description>&lt;p&gt;Listen, there isn&amp;rsquo;t much in this world that&amp;rsquo;s as luxurious as being able to pee whenever you want to. Imagine floating
in the ocean, free at the drop of a hat, except it&amp;rsquo;s anywhere. A simple luxury, but a freeing one. Or, at least it would
be, if you weren&amp;rsquo;t confined to a hospital bed.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m talking, of course, about wearing a Foley catheter.&lt;/p&gt;
&lt;p&gt;The only small downside to this absolute luxury is having a tube constantly down your urethra. You notice it constantly;
every slight repositioning of your legs means a gentle, calming, pulling sensation as if your bladder was being pulled
out the long way, with extra friction the whole way out.&lt;/p&gt;
&lt;p&gt;Funny enough, that&amp;rsquo;s also how it stays in there. Theres a little balloon at the end that gets inflated once it has
made its way all the way to your bladder. Like a ship in a bottle, it&amp;rsquo;ll stay in place, resisting being tugged.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;ship-in-bottle.png&#34; alt=&#34;Drawing of a ship in a bottle, where the bottle is my bladder and the ship is the balloon&#34;&gt;&lt;/p&gt;
&lt;p&gt;And tugged it was. Mine was the plain old gravity version, which meant that the drainage would slowly make its way out,
helped by the general downhill slope of the tubing, but occasionally, one of the nurses would have to adjust it to help
it on its way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;adjustment-needed.png&#34; alt=&#34;Showing the adjustment needed to help gravity drain my catheter&#34;&gt;&lt;/p&gt;
&lt;p&gt;No matter — what&amp;rsquo;s a slight, uncomfortable tugging among friends? It was far from the most uncomfortable thing I was
going through at the time, and unlike some of hte other sensations, at least it was pretty predictable.&lt;/p&gt;
&lt;p&gt;Predictable, at least, while I was awake.&lt;/p&gt;
&lt;p&gt;There are many terrible ways to wake up in this life. Fire alarms and the smell of smoke is one, or maybe waking up to
the sound of a kid or pet about to vomit. I&amp;rsquo;d like to offer another for consideration: the catheter tug.&lt;/p&gt;
&lt;p&gt;Sitting in my hospital bed, I must have dozed off at some point during a lull in activity, having not been sleeping
well, because I was suddenly brought crashing back to this conscious realm by a well intentioned nurse adjusting my
catheter to help it drain. There&amp;rsquo;s something so guttural about being pulled from the inside out, and the sensation
shocked me awake faster than any coffee ever could. A mix of adrenaline, shock, and friction, especially in so private
an area, immediately took my full attention, easily beating out any oxy fever dream I might have been briefly
entertaining.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve ever struggled to wake up to your first alarm, then I&amp;rsquo;ve got the perfect product for you!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As time went on, as convenient as it was, I was looking forward to not having my catheter in any more. We had been
through some difficult times together, and she (Cathy) had been a great convenience, but I knew these times couldn&amp;rsquo;t
last.&lt;/p&gt;
&lt;p&gt;I was not, however, looking forward to the removal. Though directionally it aligned better with the usual direction of
flow that I&amp;rsquo;m used to down there (thank god I was out when it was inserted), I knew it wasn&amp;rsquo;t going to be pleasant.&lt;/p&gt;
&lt;p&gt;If you somehow got access to my 3AM oxy-and-insomnia-fueled googlings in the early morning of the 26th, you&amp;rsquo;d see &amp;ldquo;hwo
are foly cathetrs removed&amp;rdquo;, quickly autocorrected and answered by Google. It&amp;rsquo;s the opposite order: deflate the balloon,
then pull it on out. The nurse was honest, and she was kind. She told me it was going to be brief, but uncomfortable.&lt;/p&gt;
&lt;p&gt;It was both.&lt;/p&gt;
&lt;p&gt;But, then it was done. While still smarting, I realized that for the first time in days, I could cross my legs without
that telltale tugging sensation. I immediately stretched my legs with a huge feeling of relief, like the first time
you&amp;rsquo;re able to stretch after a long flight in the middle seat. Only you hadn&amp;rsquo;t been in the middle seat — you were in a
hospital bed (and sometimes a recliner) with a tube all the way in you.&lt;/p&gt;
&lt;p&gt;Having it out made standing, sitting, and moving &lt;em&gt;so&lt;/em&gt; much easier. I still had my drainage tubes and pacemaker wires
going in through my belly, but it was a relieving improvement and a great step in the recovery process. My entire
process
was a series of these incremental steps, each a small action, though they each felt huge, and each victory brought me
one step closer to discharge, to getting home, and to getting better. It made the process a series of gradual
improvements that helped harden my resolve and make me readier for the next step — I made it through &lt;em&gt;that&lt;/em&gt; part,
crossing it off my list, and now I can move on to the ever reducing list of next steps.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Afterward, my nurse gave me a portable urinal and told me I had to pee on my own within six hours before they&amp;rsquo;d start
to worry. I&amp;rsquo;ve never been one to shy away from a challenge, and armed with my resolve and newly found mobility freedom,
I took it in stride, making my way through a couple cups of ice water. The urinal was awkward at first, and I&amp;rsquo;m sure the
office workers in the building across from my window saw more than they had bargained for, but we made it work and were
home free. Or, if not &lt;em&gt;home&lt;/em&gt; free, at least free to move on with everything else I had to do, free from any of those
eyebrow raising tugs.&lt;/p&gt;
&lt;p&gt;But, I&amp;rsquo;ll never forget the sensations, especially that wake up call. And that&amp;rsquo;s why, Sharks, I&amp;rsquo;d like to return to my
alarm clock idea, and with an initial investment for 10% of the company&amp;hellip;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;(&lt;a href=&#34;../&#34;&gt;Link back to heart topics&lt;/a&gt;)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How much oranger do red orange bags make oranges look?</title>
      <link>https://alexanderell.is/posts/orange/</link>
      <pubDate>Sun, 13 Apr 2025 00:39:45 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/orange/</guid>
      <description>&lt;p&gt;Look at this orange:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;orange-3-without.jpeg&#34; alt=&#34;Picture of an orange without the orange bag that they come in&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now look at this orange:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;orange-3-with.jpeg&#34; alt=&#34;Picture of an orange with the orange bag that they come in&#34;&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the same orange.&lt;/p&gt;
&lt;p&gt;But, look how much more orange it looks with the red mesh on top of it:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;orange-3.gif&#34; alt=&#34;gif going back and forth between the two&#34;&gt;&lt;/p&gt;
&lt;p&gt;If you buy bags of oranges (at least at many places in the US), they frequently come in this red mesh bag. This bag
makes the
oranges look more orange. Oranger.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what that looks like at a local grocery store&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;oranges-at-trader-joes.jpg&#34; alt=&#34;Picture of Sumo oranges for sale at Trader Joe&amp;amp;rsquo;s, with one bin in bags and the other bin just individual oranges&#34;&gt;&lt;/p&gt;
&lt;p&gt;Ripe oranges are usually oranger, so this bag makes the oranges look better than they may actually be. Maybe the secret
is to never buy bagged fruit, since it&amp;rsquo;s harder to evaluate the quality of each orange.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;style&gt;
table td, table th {
  width: 150px;
  text-align: center;
}
&lt;/style&gt;
&lt;p&gt;This made me wonder — how does the bag change how we perceive the color?&lt;/p&gt;
&lt;p&gt;I thought this difference would be visible if we did some quick and tricky digital math: what if we had a picture of the
orange with and without the bag under the same light and camera conditions, then checked the average pixel?&lt;/p&gt;
&lt;p&gt;Here are the results from 11 different orange photos, with and without the mesh:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Orange&lt;/th&gt;
&lt;th&gt;Without bag&lt;/th&gt;
&lt;th&gt;With bag&lt;/th&gt;
&lt;th&gt;Avg. color without&lt;/th&gt;
&lt;th&gt;Avg. color with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;1&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-1-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-1-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #D0530A;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #B9310A;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;2&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-2-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-2-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #B23C07;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #C1370A;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;3&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-3-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-3-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #D0570D;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #CB3E0D;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;4&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-4-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-4-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #D0570D;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #CB3E0D;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;5&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-5-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-5-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #982606;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #A82808;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;6&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-6-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-6-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #9A2B05;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #A22406;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;7&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-7-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-7-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #982207;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #A21E09;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;8&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-10-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-10-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #D55B0B;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #D73A08;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;9&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-11-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-11-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #E3550C;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #CE3507;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;10&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-12-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-12-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #C1500A;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #BD3C09;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;h2&gt;11&lt;/h2&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-13-without-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src=&#34;cropped/orange-13-with-cropped.jpeg&#34; width=&#34;100&#34; height=&#34;100&#34;/&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #C7500B;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div style=&#34;width: 100px; height: 100px; background-color: #B73E09;&#34;&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are a few interesting things here. First, the average pixel is not what I would expect it to be at all, to be
honest. I even ran the average pixel calculation a second time with more advanced calculations, including some
orange-only-masking to avoid non-orange colors, but I got similar results. They&amp;rsquo;re all much more brown than my eyes
would assume when I look at the images.&lt;/p&gt;
&lt;p&gt;Weirdly, that kind of makes sense when you look at each image closely. Here&amp;rsquo;s a &lt;strong&gt;big trypophobia warning&lt;/strong&gt;, but you can
open the spoiler below.&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Click to see a close up photo of orange skin, which is kinda weird to be honest&lt;/summary&gt;
  &lt;img src=&#34;cropped/orange-1-without-cropped.jpeg&#34;/&gt;
  &lt;p&gt;Look how much brown there really is when you look closely! Also, kind of gross.&lt;/p&gt;
&lt;/details&gt;
&lt;br&gt;
&lt;p&gt;Kinda weird, right? This kind of makes sense though — this whole thing was motivated by the feeling that our eyes are
tricked by colors, so it makes sense that our eyes are much less analytical than my computer averaging over all of the
pixels.&lt;/p&gt;
&lt;p&gt;The other interesting thing is that the addition of the red mesh clearly adds a warmth to each of the average colors.
We can see a clear shift, even for those showing up as brown.&lt;/p&gt;
&lt;p&gt;We see the RGB shift mostly in the green, interestingly enough. The average change to RGB values is around (-15, -20,
-4) with the bag, with some larger shifts in the green. That&amp;rsquo;s a little hard to visualize, but that&amp;rsquo;s the difference
between this first pale yellow and the second, more robust orange:&lt;/p&gt;
&lt;div style=&#34;width: 100px; height: 100px; background-color: rgb(242, 197, 38);&#34;&gt;&lt;/div&gt;
&lt;div style=&#34;width: 100px; height: 100px; background-color: rgb(222, 167, 32);&#34;&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;OK, maybe not exactly a robust orange, and not exactly more appetizing, but again, I think our mind is probably playing
more
tricks on us. There&amp;rsquo;s also probably a better way to think about color shifts that I&amp;rsquo;m not familiar with, but even as a
basic
measure, we can see this clear shift with the average pixels side by side.&lt;/p&gt;
&lt;p&gt;Of course, as expected,
eyes &lt;a href=&#34;https://photo.stackexchange.com/questions/10208/how-many-colors-and-shades-can-the-human-eye-distinguish-in-a-single-scene&#34;&gt;are&lt;/a&gt; &lt;a href=&#34;https://labs.psych.unr.edu/websterlab/Research.html&#34;&gt;incredibly&lt;/a&gt; &lt;a href=&#34;https://www.aao.org/eye-health/tips-prevention/how-humans-see-in-color&#34;&gt;complex&lt;/a&gt;,
and the answer is much more nuanced than the average pixel value: our eyes adapt to the environment, remember the color
of things, and change dynamically.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in a rabbit hole, I&amp;rsquo;d recommend
this &lt;a href=&#34;https://colorusage.arc.nasa.gov/Simult_and_succ_cont.php&#34;&gt;NASA Research Center Color Usage Research Lab article on simultaneous and successive contrast&lt;/a&gt;
and these this Wikipedia article
on &lt;a href=&#34;https://en.wikipedia.org/wiki/Chromatic_adaptation&#34;&gt;chromatic adaptation&lt;/a&gt;,  &lt;a href=&#34;https://en.wikipedia.org/wiki/Color_appearance_model&#34;&gt;color appearance model&lt;/a&gt;,
and &lt;a href=&#34;https://en.wikipedia.org/wiki/Color_constancy&#34;&gt;color constancy&lt;/a&gt;. It obviously extends well past a simple average
pixel color!&lt;/p&gt;
&lt;p&gt;Given that the trick is happening in our eyes, I think a better experiment would be a human-focused experiment for how
we perceive the average color. Maybe we could have two groups, with bag and without, and we show them the cropped photos
and have them pick the average (or most dominant?) color they perceive in the photo. We&amp;rsquo;d then be able to compare across
the groups to confirm that the with-bag photos skew redder.&lt;/p&gt;
&lt;p&gt;Maybe another day. I think I&amp;rsquo;ve already been staring at pictures of oranges for too long.&lt;/p&gt;
&lt;h2 id=&#34;anyways-heres-how-i-set-this-up&#34;&gt;Anyways, here&amp;rsquo;s how I set this up.&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;experimental-setup.jpg&#34; alt=&#34;My experimental setup for taking photos of oranges, with my dog looking on&#34;&gt;
&lt;em&gt;The experimental setup, with the author&amp;rsquo;s attentive assistant&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I took 11 different photos of various oranges in the same position, with and without the red mesh, and cropped the same
section of each photo.&lt;/p&gt;
&lt;p&gt;I found the pixel locations of the square I wanted, then I translated those coordinates into specific offsets for a
&lt;code&gt;sips&lt;/code&gt; command (Scriptable Image Processing System), which I learned about today. It made this programmatic cropping
very easy. For example, cropping two photos of Orange 1 in the same position, with and without mesh, as two files,
&lt;code&gt;orange-1-with.jpeg&lt;/code&gt; and &lt;code&gt;orange-1-without.jpeg&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;for f in orange-1*.jpeg; 
  do sips -c 788 740 --cropOffset 1519 1083 &amp;#34;$f&amp;#34;; 
done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This let me go from these two photos:&lt;/p&gt;
&lt;img src=&#34;orange-1-with.jpeg&#34; width=&#34;300&#34;/&gt;
&lt;br&gt;
&lt;img src=&#34;orange-1-without.jpeg&#34; width=&#34;300&#34;/&gt;
&lt;p&gt;To these two photos&lt;/p&gt;
&lt;img src=&#34;cropped/orange-1-with-cropped.jpeg&#34; width=&#34;200&#34;/&gt;
&lt;br&gt;
&lt;img src=&#34;cropped/orange-1-without-cropped.jpeg&#34; width=&#34;200&#34;/&gt;
&lt;br&gt;
&lt;p&gt;Assuming I put the mesh on without disturbing the orange, this meant that we would be doing an exact comparison between
the two.&lt;/p&gt;
&lt;p&gt;After I did this for all of the photos, with and without mesh, I then used &lt;code&gt;magick&lt;/code&gt; to calculate the average pixel
value:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ for f in *-cropped.jpeg; 
&amp;gt;   do   echo -n &amp;#34;$f: &amp;#34;;   
&amp;gt;   magick &amp;#34;$f&amp;#34; -resize 1x1 txt:- | grep -o &amp;#39;#[A-Fa-f0-9]\{6\}&amp;#39;; 
&amp;gt; done
orange-1-with-cropped.jpeg: #B9310A
orange-1-without-cropped.jpeg: #D0530A
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pretty neat!&lt;/p&gt;
&lt;p&gt;Once I found that everything was showing up a lot more brown, I also experimented with a basic Python script that
leveraged OpenCV. This script creates a mask for each image that excludes non-orange-ish pixels, defined by a range that
I define. It can then take the average over just the orange pixels that fall outside of the mask.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; file &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; files:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    image &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;imread(file)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Convert to HSV color space (better for color detection)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hsv &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cvtColor(image, cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;COLOR_BGR2HSV)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Define the range for orange in HSV. This took some tinkering to get the right values.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lower_orange &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array([&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;150&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;150&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    upper_orange &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;array([&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;255&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Mask the image to get only the orange parts.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;inRange(hsv, lower_orange, upper_orange)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    orange_pixels &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bitwise_and(image, image, mask&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;mask)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# For debugging, I saved the binary mask to visualize them.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mask_filename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(output_mask_dir, os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;basename(file)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;replace(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.jpeg&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_mask.png&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;imwrite(mask_filename, mask)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# I also saved just the orange parts to visualize it.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    orange_only_filename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(output_orange_only_dir, os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;path&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;basename(file)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;replace(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;.jpeg&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_orange.png&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;imwrite(orange_only_filename, orange_pixels)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Now, take the mean of the orange pixels with the mask, which means we&amp;#39;re (hopefully) ignoring all of the browner &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# pixels when calculating the mean.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bgr_avg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;mean(orange_pixels, mask&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;mask)[:&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Then, translate to RGB (and HSV for debugging).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rgb_avg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tuple(reversed(bgr_avg))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hsv_avg &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;cvtColor(np&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;uint8([[bgr_avg]]), cv2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;COLOR_BGR2HSV)[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(file, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Average RGB orange color:&amp;#34;&lt;/span&gt;, rgb_avg, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;HSV:&amp;#34;&lt;/span&gt;, hsv_avg)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was pretty neat, because it meant that I could mask away any non-orange pixels (like very dark shadows). That ended
up looking something like this, with the original photo, the mask, and just the orange parts that would be used for the
average:&lt;/p&gt;
&lt;img src=&#34;cropped/orange-5-with-cropped.jpeg&#34; width=&#34;300&#34;/&gt;
&lt;br&gt;
&lt;img src=&#34;masks/orange-5-with-cropped_mask.png&#34; width=&#34;300&#34;/&gt;
&lt;br&gt;
&lt;img src=&#34;orange_only/orange-5-with-cropped_orange.png&#34; width=&#34;300&#34;/&gt;
&lt;br&gt;
&lt;p&gt;I must confess, I was cheating and trying to get the CSS boxes in the table above to look more orange. This isn&amp;rsquo;t how
our eyes work, and these ended up looking more muted anyways. Maybe because I messed something up in
the translation? The average pixel values ended up being very, very similar though, so I ended up just using the
&lt;code&gt;magick&lt;/code&gt; ones in the table above. Fun to experiment with though!&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This was also a great example of how much easier this experimentation is with LLMs — being able to easily discover tools
for cropping or pixel evaluation meant that the time from idea to proof of concept was very, very short.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Even with these lackluster brown average pixels, I&amp;rsquo;m convinced that the red mesh bags make the oranges look oranger.
It&amp;rsquo;s not big enough to call the FTC for, but it is an effective little trick, a small ripeness deception that we all
have to live with.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;This post is not affiliated with &lt;a href=&#34;https://sumocitrus.com/&#34;&gt;Sumo Citrus&lt;/a&gt;, though the author would be very
interested in a sponsorship deal.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Stores also do this with green bags for avocados, and others. That&amp;rsquo;ll be the follow up paper that cites this one.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;New addition to the resume: &amp;ldquo;proficient in computer vision&amp;rdquo;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Visualizing my strengths and areas for growth with RPG radar charts</title>
      <link>https://alexanderell.is/posts/radar-charts/</link>
      <pubDate>Sat, 12 Apr 2025 12:47:16 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/radar-charts/</guid>
      <description>&lt;p&gt;Sometimes I just love a good visualization, and there&amp;rsquo;s something about radar charts that makes them so approachable.
Maybe it&amp;rsquo;s the asymmetry or the mapping to 2D space, but they give an immediate view across multiple dimensions.&lt;/p&gt;
&lt;p&gt;The other day, I was chatting with a colleague of mine about different areas you can grow in as a software engineer,
and I was reminded of something I had been thinking about for a while: what would it look like to
visualize your strengths and weaknesses with something like a radar chart? With my colleague, I sketched out a rough
diagram of what that
could look like for a few charts on a whiteboard: six different axes for technical skills, six different axes for
non-technical skills,
and six different axes for focus areas.&lt;/p&gt;
&lt;p&gt;We went through each one together, thinking through where each of us would fall on different axes. How would I rate
myself on coding, technical design, mentoring, or writing? How would he rate himself, and how would I rate him?&lt;/p&gt;
&lt;p&gt;After we had filled it in and discussed where we thought we were, we were left with pretty good visualizations of our
skill sets, where we were strong, and where we could grow more. It was an interesting exercise, and I wanted to think
through it
a little more here. What makes it a good exercise? Where does it fall short? What could make it even better?&lt;/p&gt;
&lt;h2 id=&#34;the-good-part-evaluating-my-own-strengths-and-areas-for-growth&#34;&gt;The good part: evaluating my own strengths and areas for growth&lt;/h2&gt;
&lt;p&gt;If you came to just see my strengths and weaknesses, you&amp;rsquo;ve come to the right place. I regularly take some time to
reflect and think through how I&amp;rsquo;ve grown and where I can continue to grow, and I&amp;rsquo;ve organized some recent thoughts into
a few different groupings, each with a few different axes that came to mind, visualized in radar charts.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&#34;technical-skills&#34;&gt;Technical skills&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;technical.png&#34; alt=&#34;Radar graph of my technical skills&#34;&gt;&lt;/p&gt;
&lt;p&gt;For my technical skills, I think I&amp;rsquo;m pretty good across the board. I would say my strengths lie in debugging,
creativity, and
MVPs/prototypes. I would say my coding and code review are likely the main areas for growth; for coding, I find myself
only wanting to learn just enough of the programming language to solve the problem at hand, and for code review, I could
be more diligent about both the depth of the reviews and the latency.&lt;/p&gt;
&lt;h3 id=&#34;softer-skills&#34;&gt;Soft(er) skills&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;softer.png&#34; alt=&#34;Radar graph of my soft skills&#34;&gt;&lt;/p&gt;
&lt;p&gt;For the softer skills, I think my main strengths are in writing, mentoring, technical talks, and fostering psychological
safety. I&amp;rsquo;m pretty good at leading projects and planning, but I know I can improve at off the cuff discussions (I
usually do better with writing or prepared talks) and navigating the subtleties of big company politics. Some of my
other main growth areas are networking (like meeting more people across the company and staying in touch with people
I&amp;rsquo;ve worked with in the past, not like TCP) and awareness of the industry at large, much as I may read HN.&lt;/p&gt;
&lt;h3 id=&#34;the-start-of-the-asterisks&#34;&gt;The start of the asterisks&lt;/h3&gt;
&lt;p&gt;There are a few caveats that are worth mentioning here that we can discuss later:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There&amp;rsquo;s always room to grow, even for the ones I&amp;rsquo;ve ranked higher.&lt;/li&gt;
&lt;li&gt;What&amp;rsquo;s the scale? What does 30% of the way to the edge mean?&lt;/li&gt;
&lt;li&gt;Why did I select those items? What about this other one I&amp;rsquo;m missing?&lt;/li&gt;
&lt;li&gt;How accurate is my own assessment?&lt;/li&gt;
&lt;li&gt;What do these mean? Why aren&amp;rsquo;t you 10/10 at code review?&lt;/li&gt;
&lt;li&gt;What am I doing evaluating my technical skills in my free time?&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Those are all very valid points. Even with those asterisks, assuming I can trust my own evaluation (more on this
later too), this is a neat way to see at a glance where my strengths and weaknesses are, and the visualization makes it
clear where there&amp;rsquo;s room to grow.&lt;/p&gt;
&lt;h2 id=&#34;how-effective-is-this&#34;&gt;How effective is this?&lt;/h2&gt;
&lt;h3 id=&#34;the-arbitrary-scale&#34;&gt;The arbitrary scale&lt;/h3&gt;
&lt;p&gt;The first thing that comes to mind is the arbitrary scale, like skill bars on a resume. If Bjarne Stroustrup rates
himself at 7/10 for C++, where does that leave the rest of us? Zero is easy, but 100% is obviously subjective.
Clearly I&amp;rsquo;m not at 100% for anything, except maybe for self-awareness and knowing I shouldn&amp;rsquo;t rate myself at 100% for
anything.&lt;/p&gt;
&lt;p&gt;The critical part is that &lt;strong&gt;I know what the scale is&lt;/strong&gt; (or, if working with someone else, we calibrate what we think
these
are together). I find the value to be in evaluating the space to grow, and it really doesn&amp;rsquo;t matter if your end marker
is some threshold you want to meet or the highest attainable goal in the universe. There&amp;rsquo;s no cheating here, only lying,
and
the important part for each rating is the honest reflection.&lt;/p&gt;
&lt;h3 id=&#34;a-fair-evaluation&#34;&gt;A fair evaluation&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s also the problem of fairly evaluating yourself, particularly since you may not know your own blind spots. I
think I&amp;rsquo;m pretty self-aware, but of course I&amp;rsquo;d think that. I also find it very helpful to anchor with feedback I&amp;rsquo;ve
gotten from others. While I think I&amp;rsquo;m pretty good at writing and mentorship, I&amp;rsquo;ve also gotten very positive feedback
about both, which helps me comfortably set those higher.&lt;/p&gt;
&lt;p&gt;On the other end of that spectrum, I&amp;rsquo;m also likely my harshest critic, and I may be ranking some areas unfairly
lower. Take my technical abilities, for example, where I&amp;rsquo;m likely subconsciously comparing myself to extremely gifted
engineers that I&amp;rsquo;ve worked with or seen in the past. Ironically this also seemed like a good spot to cite the Bjarne
Stroustrup anecdote, but I hesitated at first, feeling that my own self-evaluation skills must be much worse than his!&lt;/p&gt;
&lt;h3 id=&#34;honesty-and-vulnerability&#34;&gt;Honesty and vulnerability&lt;/h3&gt;
&lt;p&gt;Both of the above points have an underlying emphasis on honesty and vulnerability, a key part of doing this exercise. On
your own, I think it&amp;rsquo;s a great way to reflect, but as an exercise with someone else, it requires a good level
of trust and psychological safety, with that extra mental step to clearly call out your shortcomings. It&amp;rsquo;s OK to have
room to grow, and it should be OK with whoever you&amp;rsquo;re working with as well.&lt;/p&gt;
&lt;p&gt;That does make it feel like it wouldn&amp;rsquo;t translate well into relationships without trust or with power imbalances. For
example, if I was a junior engineer sending out my resume to hiring managers, I wouldn&amp;rsquo;t want to tell anyone that my
coding skills are a 1/10. If I didn&amp;rsquo;t have psychological safety and was worried that my manager was going to fire me
for any displayed weakness, I would be much less comfortable being honest.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve shared my own evaluation because I value transparency and I&amp;rsquo;m in a very fortunate position to lead by example for
psychological safety. I have a strong work history and confidence in my abilities, and really if anyone reads this and
doesn&amp;rsquo;t want to hire me because I didn&amp;rsquo;t rate my coding abilities as 10/10, it likely wouldn&amp;rsquo;t have been a good fit
anyways!&lt;/p&gt;
&lt;h3 id=&#34;what-do-you-even-include-for-the-axes&#34;&gt;What do you even include for the axes?&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s clearly not valuable to put everything up here, and I&amp;rsquo;ve omitted things like professionalism, patience, or time
management. For
myself, I can assume those are pretty much all taken care of, though my time management definitely didn&amp;rsquo;t used to be as
good as it is now. For someone else, maybe time management is something they&amp;rsquo;re actively working on and they should
track it for themselves.&lt;/p&gt;
&lt;p&gt;There are also things that might just not be worth including. Should I include &amp;ldquo;computer graphics&amp;rdquo; if it&amp;rsquo;s just going to
be marked as a zero anyways? Maybe if I&amp;rsquo;m doing some introspection on my familiarity with different areas in computer
science, but maybe not if I&amp;rsquo;m evaluating my abilities with the lens of my current role.&lt;/p&gt;
&lt;p&gt;In case it&amp;rsquo;s not clear by now, this is also not &amp;ldquo;Alex&amp;rsquo;s exhaustive list of the things that every software engineer
needs&amp;rdquo;. These are skills and areas that I&amp;rsquo;ve noticed in my day to day job that come into play. The ones I&amp;rsquo;ve included
are different for different roles, and they&amp;rsquo;re not all needed at different companies. They also come with huge asterisks;
who
cares about office politics really? I don&amp;rsquo;t care in the grand scheme of things, but I care in the scope of understanding
how the game works so I can ensure a coworker of mine gets proper recognition for the important work they&amp;rsquo;re doing or
help a junior colleague get promoted.&lt;/p&gt;
&lt;p&gt;Along those lines, I think it&amp;rsquo;s also an interesting exercise to do this for different categories. I included the
ones I&amp;rsquo;ve done for technical skills and soft skills, with a subset of the axes for each, but there are a few other ones
that would be great to work through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Different services your team manages and interacts with&lt;/li&gt;
&lt;li&gt;Different frameworks, technologies, and subareas for your role.
&lt;ul&gt;
&lt;li&gt;For a networking role, this could include Wireshark, Netty, HTTP/2, QUIC, security, DDoS attacks, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Various focus areas within software engineering
&lt;ul&gt;
&lt;li&gt;E.g. databases, data engineering, front-end, mobile development, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Various focus areas within computer science
&lt;ul&gt;
&lt;li&gt;E.g. compilers, networking, graphics, etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;where-does-the-radar-chart-fall-short-and-how-could-it-be-better&#34;&gt;Where does the radar chart fall short, and how could it be better?&lt;/h3&gt;
&lt;p&gt;I think it&amp;rsquo;s a pretty neat visualization, but there are some clear drawbacks.&lt;/p&gt;
&lt;p&gt;One is that there&amp;rsquo;s some obvious crossover between axes that isn&amp;rsquo;t represented in these charts. Empathy and
communication skills are the building blocks for mentoring, writing, technical talks, leading, and much more. Breaking
them up into these more concrete areas makes it easier to think about how to grow them; it&amp;rsquo;s easier to work with &amp;ldquo;I want to
practice my technical talks&amp;rdquo; than an amorphous &amp;ldquo;I want to improve my general communication skills&amp;rdquo;. But having them as
discrete axes loses some of that foundational, crossover effect, where improving at one will likely make you better at
another.&lt;/p&gt;
&lt;p&gt;Another drawback is the implied relationships between axes on the chart. For one, it appears to give the same weight to
two areas that may not have the same importance. I&amp;rsquo;ve included both &amp;ldquo;networking&amp;rdquo; and &amp;ldquo;mentoring&amp;rdquo; above on the same
graph, but being a good mentor is so much more important to me. Additionally, things next to each other feel like they
should be related, even though the axes above are independent.&lt;/p&gt;
&lt;p&gt;I think an interesting thought exercise is to think about the order of the categories. Maybe grouping similar items
together would allow that crossover effect to be more visible, or maybe you can think about steps in a process. For
example, you could look at the different parts of a project and how good you are at the different areas (these are
arbitrary values, not necessarily my own evaluation):&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;project.png&#34; alt=&#34;Radar graph of the different parts of a project&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;With those caveats, I think it&amp;rsquo;s a neat visualization. Like writing, one of the real benefits of this exercise is the
thinking that it forces you to do, particularly as you do an honest evaluation.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no perfect way to do it, but I like these visuals.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Shout out to charts.livegap.com for their free radar graph generator tool that I used for this post. I&amp;rsquo;m not
affiliated, and for full disclosure, I used their site with an adblocker. You can try it out for
yourself &lt;a href=&#34;https://charts.livegap.com/v2/app.php?lan=en&amp;amp;gallery=radar&#34;&gt;here&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Harry Chapin and RATM: the problem with bleeding interests</title>
      <link>https://alexanderell.is/posts/playlist/</link>
      <pubDate>Sun, 28 Apr 2024 14:55:20 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/playlist/</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: this post requires a basic familiarity with the songs discussed. If you&amp;rsquo;re not familiar with them, I&amp;rsquo;d
recommend you listen to at least a portion of the following three songs:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Free Bird&lt;/strong&gt; by Lynyrd Skynyrd (&lt;a href=&#34;https://www.youtube.com/watch?v=0LwcvjNJTuM&#34;&gt;YouTube&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Cat&amp;rsquo;s in the Cradle&lt;/strong&gt; by Harry Chapin (&lt;a href=&#34;https://youtu.be/etundhQa724?si=7NnIVnqg1zTxsi2w&amp;amp;t=65&#34;&gt;YouTube&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Killing In The Name&lt;/strong&gt; by Rage Against The Machine (&lt;a href=&#34;https://www.youtube.com/watch?v=bWXazVhlyxQ&#34;&gt;YouTube&lt;/a&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;There are many musical genres I like, and what I listen to usually depends on my mood at the time. Some days, I want to
listen to Miles Davis, and some days, I want to listen to Slayer.&lt;/p&gt;
&lt;p&gt;Some days, I just want to play some normcore Southern rock. The other day, I opened Spotify, went to &lt;em&gt;Free Bird&lt;/em&gt;, and
selected &amp;ldquo;Go to radio&amp;rdquo;.  It gave me a playlist, and after playing on shuffle for a few songs, I realized something was
wrong.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;playlist.PNG&#34; alt=&#34;A screenshot of a playlist generated by Spotify for Free Bird that includes Harry Chapin and Rage Against The Machine&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;What&amp;rsquo;s the problem here? It&amp;rsquo;s a little tag at the top of the playlist, just out of sight in that screenshot: &lt;em&gt;&amp;ldquo;Made for Alex&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Let me start the rest of this complaint with full acknowledgement that I have nearly zero knowledge of
recommendation algorithms. But, to me, this is a problem with personalized recommendations that are over-indexed
on your interests. In particular, when these interests are very disparate; I don&amp;rsquo;t think I would ever
expect a human to create a playlist with nearly polar vibe opposites &lt;em&gt;Cat&amp;rsquo;s in the Cradle&lt;/em&gt; and &lt;em&gt;Killing In The Name&lt;/em&gt;.
Though they can be loosely connected to the song the playlist was based off of, they clearly don&amp;rsquo;t fit together.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve probably searched for and clicked play on every song in that screenshot, and there&amp;rsquo;s the problem. They have a clear,
explicit signal that these are songs I&amp;rsquo;m likely to like, and there&amp;rsquo;s no signal for interest like an explicit signal
from the user. But, by focusing on this, this means that the source, this &lt;em&gt;Free Bird&lt;/em&gt; track, counts less.  Worse still,
it means that it&amp;rsquo;s quick to bleed interests, genres, and vibes, with songs that not only don&amp;rsquo;t fit, but harshly clash.&lt;/p&gt;
&lt;p&gt;It could come back to the philosophical question about the goal of a song radio or playlist, but I don&amp;rsquo;t think that even
fully explains it. If you&amp;rsquo;re using it as a way to listen to songs you like, this could work, minus the genre clashing.
If you&amp;rsquo;re using it as a way to discover new, similar music, then it really doesn&amp;rsquo;t work. It leans towards things I
already know about, and by prioritizing those, it&amp;rsquo;s omitting songs I don&amp;rsquo;t know I don&amp;rsquo;t know. In this case, I wish there
was a separate way to generate these playlists: &amp;ldquo;ignore everything I&amp;rsquo;ve ever told you before and just give me songs that
are similar to this one&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;In all of my Googling so far, there doesn&amp;rsquo;t seem to be an easy way to turn this off. I&amp;rsquo;m also not the
&lt;a href=&#34;https://community.spotify.com/t5/Content-Questions/Stop-personalizing-playlists/td-p/5628488&#34;&gt;only&lt;/a&gt;
&lt;a href=&#34;https://community.spotify.com/t5/Your-Library/Playlists-without-personalization/td-p/5327645&#34;&gt;one&lt;/a&gt;
&lt;a href=&#34;https://community.spotify.com/t5/Content-Questions/PLEASE-let-us-turn-off-personalized-playlists-I-want-to-listen/td-p/5810146&#34;&gt;asking&lt;/a&gt;
&lt;a href=&#34;https://community.spotify.com/t5/iOS-iPhone-iPad/Stop-personalizing-spotify-made-playlist-for-me/td-p/4668562&#34;&gt;about&lt;/a&gt;
this.&lt;/p&gt;
&lt;p&gt;Spotify&amp;rsquo;s new &lt;a href=&#34;https://newsroom.spotify.com/2023-09-12/ever-changing-playlist-daylist-music-for-all-day/&#34;&gt;daylist&lt;/a&gt; sounds
like it could be what I&amp;rsquo;m looking for, as it&amp;rsquo;s a more &amp;ldquo;match the vibe&amp;rdquo; playlist. For example, it says I&amp;rsquo;ve previously
listened to pop art and soundscape on Sunday afternoons, and it created a playlist with &amp;ldquo;siren, haunting, delicate,
soothing, vocal, and songwriter&amp;rdquo;. All great adjectives, but what if this Sunday I&amp;rsquo;m driving in Boston traffic instead of
doing some delicate vibing?&lt;/p&gt;
&lt;p&gt;This is all likely a sign that there are better tools out there for new music discoverability, and I should go discover
them too.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In closing, please enjoy:&lt;/p&gt;
&lt;iframe style=&#34;border-radius:12px&#34; src=&#34;https://open.spotify.com/embed/playlist/2gl6sNjo7oynxLanGDeV7A?utm_source=generator&#34; width=&#34;100%&#34; height=&#34;352&#34; frameBorder=&#34;0&#34; allowfullscreen=&#34;&#34; allow=&#34;autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture&#34; loading=&#34;lazy&#34;&gt;&lt;/iframe&gt;
</description>
    </item>
    
    <item>
      <title>All of the writing I did in a week as a software engineer</title>
      <link>https://alexanderell.is/posts/writing-swe/</link>
      <pubDate>Sat, 18 Nov 2023 11:57:12 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/writing-swe/</guid>
      <description>&lt;p&gt;The other day, I was thinking about less-obvious skills that I find helpful for
working as a software engineer. Some skills are obvious, like understanding
technical topics, learning new things, and thinking like a computer, but there&amp;rsquo;s
a separate class of unobvious skills that an outsider may not immediately think
of when they imagine a career in software.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Empathy and communication are the main ones that come to mind.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The other day, I was thinking through what communication looks like for my day
job. Much of it is talking with other people, be it occasional on-site hallway
conversations, 1:1s, meetings, or presentations. A great deal of it is also in
writing, especially as I work from home. I have a general feeling that I do a
lot of writing, but as an experiment, I thought it would be interesting to look
at all of the writing I did in a given week.&lt;/p&gt;
&lt;p&gt;Quick caveat that I&amp;rsquo;m also pretty biased towards writing. I like to write, so I
write more, so I get a lot of practice writing, so I get better at the process,
so it&amp;rsquo;s easier for me to write, and I end up liking it more. You can see where
I&amp;rsquo;m going with this.&lt;/p&gt;
&lt;h3 id=&#34;a-day-in-the-life&#34;&gt;A day in the life&lt;/h3&gt;
&lt;p&gt;For a little context on my work, I currently work as a software engineer on the
Cloud Gateway team at Netflix. Our team runs the big proxies at the front door
— we write services that are like automated switchboard operators, routing your
browser&amp;rsquo;s request for &lt;code&gt;www.netflix.com&lt;/code&gt; to the right place. We have a few
different proxies, and I&amp;rsquo;m roughly the tech lead for one of them. My time is
split between current work (both as an individual contributor and project lead),
forward looking work, partnerships, mentorship, security, incidents, and
on-call.&lt;/p&gt;
&lt;p&gt;Alongside my regular day job work, I was also on call this last week&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, which
entails answering support questions (our team&amp;rsquo;s immediate users are internal
teams), responding to incidents, and keeping an eye on general software health
for our services. This led to a little more writing than usual, as you&amp;rsquo;ll see.&lt;/p&gt;
&lt;h3 id=&#34;cat_typinggif&#34;&gt;cat_typing.gif&lt;/h3&gt;
&lt;p&gt;In the last week, I wrote:&lt;/p&gt;
&lt;h4 id=&#34;zero-emails&#34;&gt;Zero emails&lt;/h4&gt;
&lt;p&gt;I actually don&amp;rsquo;t write many emails for work, as most of our async communication
is around Slack. This last week, I wrote exactly 0.&lt;/p&gt;
&lt;h4 id=&#34;a-few-dozen-lines-of-code&#34;&gt;A few dozen lines of code&lt;/h4&gt;
&lt;p&gt;This was a pretty light coding week, mostly because 1) I was on call and 2) I&amp;rsquo;m
currently working on one of those &amp;ldquo;investigate for a week, then fix with a few
lines of code&amp;rdquo; problems.&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; This writing included the code, tests, comments,
and PR descriptions, as well as comments I left in PR reviews.&lt;/p&gt;
&lt;h4 id=&#34;a-handful-of-jira-tickets&#34;&gt;A handful of Jira tickets&lt;/h4&gt;
&lt;p&gt;We loosely track our work items in Jira, and I added a few, mostly summarizing a
few future work items. I also provided updates on existing tickets. Not much to
write home about here.&lt;/p&gt;
&lt;h4 id=&#34;a-feedback-form-for-the-new-grad-on-our-team&#34;&gt;A feedback form for the new grad on our team&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;m the mentor for a new grad that joined our team in August, and this week I
had to submit a feedback form for their first three months and the new grad
program as a whole. This was pretty straightforward, as we&amp;rsquo;ve had plenty of
feedback conversations and much of it was top of mind already.&lt;/p&gt;
&lt;h4 id=&#34;a-couple-of-investigation--debugging-docs&#34;&gt;A couple of investigation &amp;amp; debugging docs&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;m a huge fan of taking notes while debugging. They help me be methodical about
my investigations and capture context with what I&amp;rsquo;ve seen, my train of thought,
and what I intend to try next (and why). Though they&amp;rsquo;re mostly for myself, I
find it&amp;rsquo;s also helpful for sharing that context with others. I especially like
showing the thought process for folks who are newer to the kind of deep
debugging that we do, just to show one of the ways to dive into a gnarly
problem.&lt;/p&gt;
&lt;p&gt;This week I wrote ~15 Google doc pages of investigation notes, with the caveat
that these included many diagrams that padded my page count. I also usually use
the pageless format, since I have lots of images that would otherwise lead to
weird blank space. These docs are a lot of things like &lt;em&gt;&amp;ldquo;Here&amp;rsquo;s what I&amp;rsquo;m seeing.
My current hypothesis is X for these reasons — what about if we try Y? How does
Z work?&amp;rdquo;&lt;/em&gt;. As I mentioned before, my main work this week was spent mostly
focused on one gnarly problem in an area I wasn&amp;rsquo;t previously super familiar
with, leading to a lot of investigation.&lt;/p&gt;
&lt;p&gt;As part of this, I also put together a few diagrams summarizing the system&amp;rsquo;s
architecture, mostly for my own knowledge. This is tangentially writing — it&amp;rsquo;s
more like writing a name in a box, then putting it in the right place and
drawing arrows to its neighbors.&lt;/p&gt;
&lt;p&gt;For more on this, see &lt;a href=&#34;https://jvns.ca/blog/2022/12/21/new-zine--the-pocket-guide-to-debugging/&#34;&gt;Julia Evans&amp;rsquo; masterful zine on debugging&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;a-few-technical-docs&#34;&gt;A few technical docs&lt;/h4&gt;
&lt;p&gt;I find design documents extremely helpful for thinking through a hard problem
and sharing context and ideas with others. I find that if I can&amp;rsquo;t clearly
explain a problem and my solution, I don&amp;rsquo;t really understand it. Again, this is
probably my bias towards writing, but I find it relatively easy to knock out a
doc, and the benefit for me greatly outweighs the cost.&lt;/p&gt;
&lt;p&gt;There are many kinds of technical docs. This week, I wrote the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A forward looking strategy doc for one of our services&lt;/li&gt;
&lt;li&gt;A forward looking &amp;ldquo;what if we built this feature&amp;rdquo; doc following a conversation
with a partner&lt;/li&gt;
&lt;li&gt;A rough draft of a design doc to explain exactly what pieces we&amp;rsquo;ll need for a
new integration&lt;/li&gt;
&lt;li&gt;A technical design doc exploring a problem we&amp;rsquo;re seeing and some potential
solutions&lt;/li&gt;
&lt;li&gt;Minor updates to a previous design doc based on feedback&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;These added up to ~15 Google doc pages or so total, with some diagrams in there
as well.&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;In this category, I&amp;rsquo;d also include comments on docs, both for documents I&amp;rsquo;ve
reviewed and comments I&amp;rsquo;ve responded to on my own docs. It&amp;rsquo;s a little hard to
quantify these, but I&amp;rsquo;d say in the low tens, ranging from one-liners to
paragraphs.&lt;/p&gt;
&lt;h4 id=&#34;397-slack-messages&#34;&gt;397 Slack messages&lt;/h4&gt;
&lt;p&gt;Ahh, so this is where my time this week went. We have a Slack-heavy culture, but
400 still seems a lot. The breakdown was as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;109 messages in our support channels.
&lt;ul&gt;
&lt;li&gt;These are mostly answering questions, explaining things, asking clarifying
questions, and sharing metrics and traces. This entails a lot of translating
our team-internal things to folks on other teams.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;68 messages in our team channel
&lt;ul&gt;
&lt;li&gt;We tend to have a lot of async discussions in our team channel. I also err
on the side of sharing context with the team, leaving breadcrumbs for
anyone&amp;rsquo;s future searches.&lt;/li&gt;
&lt;li&gt;A healthy amount of these were just emojis, too.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;18 messages in our team&amp;rsquo;s notifications channel, which we use for alerts,
CI/CD notifications, and general on-call things&lt;/li&gt;
&lt;li&gt;174 direct messages
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;m pretty sure a lot of this is just pasting links or snippets for people
while I&amp;rsquo;m in 1:1s with them.&lt;/li&gt;
&lt;li&gt;This includes 20 messages in a group chat helping to debug a gnarly issue&lt;/li&gt;
&lt;li&gt;This also includes 10 messages to myself, which I use as a scratchpad for
saving things for later&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;28 channels in miscellaneous channels&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;key-takeaways&#34;&gt;Key takeaways&lt;/h3&gt;
&lt;p&gt;The biggest takeaway is that my split keyboard was a very worthwhile investment.&lt;/p&gt;
&lt;p&gt;Other than that, I don&amp;rsquo;t think there are any, really, other than the fact that I
do a lot of thinking and typing. This is just an interesting experiment to see
how much writing I actually do in a given week. This week was weighted pretty
heavily towards Slack (due to the on-call support) and investigation docs, but
it&amp;rsquo;s not out of the ordinary.&lt;/p&gt;
&lt;p&gt;I find writing immensely helpful, and I&amp;rsquo;ll always recommend it for thinking
through a problem and sharing your thoughts with others. If you find yourself
wanting to get better at writing, I would absolutely encourage you to do so.
Read a book on it, write more, and edit more.&lt;/p&gt;
&lt;p&gt;But do get an ergonomic keyboard setup.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;It&amp;rsquo;s like explaining to my mom that even though I write code, I still talk
with other humans quite a bit!&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;I&amp;rsquo;m also of the opinion that the most important part of communication is
empathy, but that&amp;rsquo;s a post for another day.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Also maybe stubbornness? But not stubbornness like &amp;ldquo;I&amp;rsquo;m right and I have
to be right and I won&amp;rsquo;t listen to you&amp;rdquo;, but more &amp;ldquo;I&amp;rsquo;ve spent two days
digging into this slowly making progress and I&amp;rsquo;m not going to give up&amp;rdquo;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;I&amp;rsquo;m also on call through the rest of the weekend. Please watch Netflix
responsibly.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;Again, stubbornness!&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;This is now motivating me to push for being paid by the page, both the
writing and the on-call.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Brute-forcing the NYT Digits game</title>
      <link>https://alexanderell.is/posts/digits/</link>
      <pubDate>Tue, 11 Jul 2023 22:06:31 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/digits/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;You&amp;rsquo;re given a list of integers and a target number. You can add, subtract, multiply, or divide numbers, and you can use the result for future operations. Once you have used a number in an operation, you may not use it again.
Your task is to find a combination of arithmetic steps to get to the target number.&lt;/p&gt;
&lt;p&gt;For example, if you&amp;rsquo;re given &lt;code&gt;[1, 2, 3]&lt;/code&gt; and the target is &lt;code&gt;9&lt;/code&gt;, a valid path would be the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Current numbers: [1, 2, 3]
(1 + 2) -&amp;gt; 3, current numbers now [3, 3]
(3 * 3) -&amp;gt; 9, current numbers now [9], target reached
Resulting in the path: [(1 + 2), (3 * 3)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Given the input &lt;code&gt;[5, 7, 11, 19, 23, 25]&lt;/code&gt; and the target number &lt;code&gt;438&lt;/code&gt;, find a path to the target.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Is this your third algorithm interview question in a hellish day of interviewing for a programming job where you won&amp;rsquo;t really see problems like this?&lt;/p&gt;
&lt;p&gt;No!&lt;/p&gt;
&lt;p&gt;This is the new New York Times game, &lt;em&gt;Digits&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;digits.png&#34; alt=&#34;Screenshot of the New York Times Digits game&#34;&gt;&lt;/p&gt;
&lt;p&gt;This game recently came out, and after playing for a while, I got to thinking more about the game.&lt;/p&gt;
&lt;p&gt;Part of the game is that you progress through 5 different combinations of input lists and targets. For example, in the above screenshot, you can see that I already solved the &lt;code&gt;156&lt;/code&gt; puzzle. When I first started playing, I assumed the difficulty would progress as you went through them, with 62 being the easiest and 438 being the hardest.&lt;/p&gt;
&lt;p&gt;This got me thinking: this is a very non-linear game, where it can sometimes feel like you make very little progress. How would you define difficulty for these problems?&lt;/p&gt;
&lt;h2 id=&#34;digits--difficulty&#34;&gt;Digits &amp;amp; difficulty&lt;/h2&gt;
&lt;p&gt;In the real game, you get 1-3 stars depending on how close you get to the target, with 3 being an exact match. For now, let&amp;rsquo;s just look at getting exactly to the target. I&amp;rsquo;m mostly interested in the 3-star solutions, and there are plenty of other areas to add more complexity to if we really wanted to.&lt;/p&gt;
&lt;p&gt;The first thing that came to mind is that there must be something related to the number of solutions for each combination of operations. You have to find &lt;em&gt;a&lt;/em&gt; combination, not the shortest combination, so as long as you find one combination that gets you to the target number, you get full points. That made me think that the more possible combinations that get you to the target there are, the easier the problem would feel.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say you&amp;rsquo;re given &lt;code&gt;[1, 2, 3]&lt;/code&gt;, and your target is &lt;code&gt;6&lt;/code&gt;. Depending on how we do the order and deduplication, there are multiple ways to get there:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;2 * 3 -&amp;gt; 6 ✓

1 + 2 -&amp;gt; 3
3 + 3 -&amp;gt; 6 ✓

1 + 3 -&amp;gt; 4
2 + 4 -&amp;gt; 6 ✓

2 + 3 -&amp;gt; 5
5 + 1 -&amp;gt; 6 ✓
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But, let&amp;rsquo;s say you&amp;rsquo;re given the same input, but your target is &lt;code&gt;7&lt;/code&gt;. There&amp;rsquo;s only really one path to get there, if we consider &lt;code&gt;2*3&lt;/code&gt; to be the same as &lt;code&gt;3*2&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;2 * 3 -&amp;gt; 6
6 + 1 -&amp;gt; 7 ✓
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Does that mean the first one is easier? I think that could be a rough heuristic, though it doesn&amp;rsquo;t really take into account the size of the number (with smaller numbers seeming easier) or the complexity of the operations, where it seems easier to just add all of the numbers together (just spamming the &lt;code&gt;+&lt;/code&gt; button) than it does to multiple two numbers, multiply two other numbers together, then add the products.&lt;/p&gt;
&lt;p&gt;What would this look like for the real problem? How would we go about estimating the size of the solution space?&lt;/p&gt;
&lt;h2 id=&#34;what-would-the-computer-say&#34;&gt;What would the computer say?&lt;/h2&gt;
&lt;p&gt;Because I spend a lot of my time trying to convince computers to do what I want, my next thought was &amp;ldquo;how do I get the computer to do it for me?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I joked earlier about it being like an algorithm interview problem, but its saving graces were that 1) it was something I was curious about, 2) it wasn&amp;rsquo;t something I was being asked to do, and 3) there was no cursed relationship between this toy problem and my ability to buy expensive cheese. With these three pillars of playful coding established, I thought a bit about how I&amp;rsquo;d go about solving this.&lt;/p&gt;
&lt;h3 id=&#34;brutal-force&#34;&gt;Brutal force&lt;/h3&gt;
&lt;p&gt;My first thought was some weird combination of calculating all of the possible representations of these operations. Doing &lt;code&gt;2 * 3 -&amp;gt; 6&lt;/code&gt; and &lt;code&gt;6 + 1 -&amp;gt; 7&lt;/code&gt; really looked like this if you write it out with the order of operations:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(2 * 3) + 1 -&amp;gt; 7&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;What would all possible paths look like? Maybe we could think about how to programmatically create the string, then feed it into &lt;code&gt;eval&lt;/code&gt; and call it a day. We can also swap in an &lt;code&gt;op&lt;/code&gt; placeholder for the operators, since we&amp;rsquo;d potentially have to try all 4. This was getting pretty weird, pretty quickly:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;1 op (2 op 3)
(1 op 2) op 3
(1 op 3) op 2
1 op (3 op 2)
... 8 other combinations
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, some of these combinations wouldn&amp;rsquo;t be valid. &lt;code&gt;Digits&lt;/code&gt; doesn&amp;rsquo;t let you do &lt;code&gt;3 / 2&lt;/code&gt; or &lt;code&gt;2 - 3&lt;/code&gt;, as it restricts you to dealing with just positive integers here. But still, this is already getting hairy for just 3 numbers in the input list, and once I started thinking about what it would look like for 6 numbers, it got even worse. Nested parentheses, since you could keep multiplying, and maybe you&amp;rsquo;d have to try swapping each operand too, since order matters for &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I spent a little time thinking through what this would look like. Maybe some sort of odd graph? Visions of state machines came to mind, some cursed thing like the following with a bunch of conditional transitions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;state-machine.png&#34; alt=&#34;Diagram of some weird state machine showing the possible combinations for this complicated string&#34;&gt;&lt;/p&gt;
&lt;p&gt;Gross. We could probably figure something out here, but do you really want to keep track of everything for all of the combinations for six digits?&lt;/p&gt;
&lt;h3 id=&#34;recursion-and-representing-the-game-as-a-tree&#34;&gt;Recursion and representing the game as a tree&lt;/h3&gt;
&lt;p&gt;It was around here that I took my dog for a short walk, and I found myself asking the dangerous question: &amp;ldquo;what if we used recursion?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;If you start with a list of numbers like &lt;code&gt;[1, 2, 3]&lt;/code&gt;, once you do an operation, you&amp;rsquo;re really left with the same problem, just with a smaller list. For example,&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Current list: [1, 2, 3], target: 7
2 * 3 -&amp;gt; 6

Current list: [1, 6], target: 7
1 + 6 -&amp;gt; 7

Current list: [7], target: 7, target has been found
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s the happy path, but we can imagine branching at every step for 1) each combination of operands and 2) each operator. It would look like this diagram, but much more complicated — each subproblem would have many more branches off of it for each (operand, operator, operand) combination.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;tree.png&#34; alt=&#34;Diagram of tree showing how the state would evolve &#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;This is actually similar to how you play the game.  You start with &lt;code&gt;[1, 2, 3]&lt;/code&gt;, you add &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;3&lt;/code&gt;, then you&amp;rsquo;re left with &lt;code&gt;[4, 2]&lt;/code&gt;, and you can go from there. There are many paths through this tree with many outcomes that don&amp;rsquo;t lead to the target number — some are included in light red — but there are paths (at least in this case) that do end up with the target number.&lt;/p&gt;
&lt;p&gt;Are we guaranteed to have a path? For the &lt;em&gt;Digits&lt;/em&gt; game, I&amp;rsquo;m sure they always pick a target that you can get to in a number of ways, but mathematically speaking, there&amp;rsquo;s no guarantee at all. We could have tried &lt;code&gt;8&lt;/code&gt; as the target above, which would have no solution.&lt;/p&gt;
&lt;p&gt;Thinking with trees and recursion made this problem much easier to visualize and translate to a computational solution. Breaking this down, we&amp;rsquo;ll basically do the following for each subproblem in our function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check if the target is in the list of numbers. If it is, we&amp;rsquo;re done.&lt;/li&gt;
&lt;li&gt;Otherwise, go through every pair of numbers.&lt;/li&gt;
&lt;li&gt;For each operator for each pair, calculate the new resulting number and call the function recursively with that number in the list instead of the pair.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;The pseudocode would look like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;operators &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;check_combo&lt;/span&gt;(numbers, target, steps_so_far):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Check if we already have the target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; target &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; numbers:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Found solution!&amp;#39;&lt;/span&gt;, steps_so_far)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Try each of the combinations of operands with each operator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; index, number &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; enumerate(numbers):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; second_index, second_number &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; enumerate(numbers):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Though not with itself.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; index &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; second_index:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Get a list of the other numbers to pass to the recursive call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      other_numbers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; numbers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;copy()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      other_numbers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      other_numbers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(second_number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; operators:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# We can do a basic check here to make sure that&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# the operation is valid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; is_valid(number, operator, second_number):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Turn it into &amp;#39;2 * 3&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        operation_string &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(number,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                           operator,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                           second_number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Do not ever do this in any code that isn&amp;#39;t&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# low-effort, throwaway blog post code.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        new_number &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(eval(operation_string))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        check_combo(other_numbers &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; [new_number],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    target,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    steps_so_far &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; [operation_string])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# We&amp;#39;ll want to ensure that the operations for the given&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# operands are valid for the rules of the game.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is_valid&lt;/span&gt;(first_operand, operator, second_operand):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# No negatives allowed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; first_operand &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; second_operand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Also, integers only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; first_operand &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; second_operand &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;check_combo([&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;], &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, [])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh wait, that&amp;rsquo;s not pseudocode, it&amp;rsquo;s Python! Running it yields the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Found solution! [&amp;#39;1 + 2&amp;#39;, &amp;#39;3 + 3&amp;#39;]
Found solution! [&amp;#39;1 + 2&amp;#39;, &amp;#39;3 + 3&amp;#39;]
Found solution! [&amp;#39;1 * 2&amp;#39;, &amp;#39;3 * 2&amp;#39;]
Found solution! [&amp;#39;1 * 2&amp;#39;, &amp;#39;2 * 3&amp;#39;]
Found solution! [&amp;#39;1 + 3&amp;#39;, &amp;#39;2 + 4&amp;#39;]
Found solution! [&amp;#39;1 + 3&amp;#39;, &amp;#39;4 + 2&amp;#39;]
Found solution! [&amp;#39;1 * 3&amp;#39;, &amp;#39;2 * 3&amp;#39;]
Found solution! [&amp;#39;1 * 3&amp;#39;, &amp;#39;3 * 2&amp;#39;]
Found solution! [&amp;#39;2 + 1&amp;#39;, &amp;#39;3 + 3&amp;#39;]
Found solution! [&amp;#39;2 + 1&amp;#39;, &amp;#39;3 + 3&amp;#39;]
Found solution! [&amp;#39;2 * 1&amp;#39;, &amp;#39;3 * 2&amp;#39;]
Found solution! [&amp;#39;2 * 1&amp;#39;, &amp;#39;2 * 3&amp;#39;]
Found solution! [&amp;#39;2 / 1&amp;#39;, &amp;#39;3 * 2&amp;#39;]
Found solution! [&amp;#39;2 / 1&amp;#39;, &amp;#39;2 * 3&amp;#39;]
Found solution! [&amp;#39;2 + 3&amp;#39;, &amp;#39;1 + 5&amp;#39;]
Found solution! [&amp;#39;2 + 3&amp;#39;, &amp;#39;5 + 1&amp;#39;]
Found solution! [&amp;#39;2 * 3&amp;#39;]
Found solution! [&amp;#39;3 + 1&amp;#39;, &amp;#39;2 + 4&amp;#39;]
Found solution! [&amp;#39;3 + 1&amp;#39;, &amp;#39;4 + 2&amp;#39;]
Found solution! [&amp;#39;3 * 1&amp;#39;, &amp;#39;2 * 3&amp;#39;]
Found solution! [&amp;#39;3 * 1&amp;#39;, &amp;#39;3 * 2&amp;#39;]
Found solution! [&amp;#39;3 / 1&amp;#39;, &amp;#39;2 * 3&amp;#39;]
Found solution! [&amp;#39;3 / 1&amp;#39;, &amp;#39;3 * 2&amp;#39;]
Found solution! [&amp;#39;3 + 2&amp;#39;, &amp;#39;1 + 5&amp;#39;]
Found solution! [&amp;#39;3 + 2&amp;#39;, &amp;#39;5 + 1&amp;#39;]
Found solution! [&amp;#39;3 * 2&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These all look like valid solutions, but there are two pretty clear problems. First, some of these operations are redundant. &lt;code&gt;1 * 2&lt;/code&gt; is technically a valid step before doing &lt;code&gt;2 * 3&lt;/code&gt;, but we can ignore redundant ones to keep our solution set a little crisper. Additionally, a lot of these operations are commutative — &lt;code&gt;3 + 2&lt;/code&gt; is the same as &lt;code&gt;2 + 3&lt;/code&gt;, so we can add some basic checks for that as well.&lt;/p&gt;
&lt;p&gt;With those in place, it may look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;operators &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;check_combo&lt;/span&gt;(numbers, target, steps_so_far):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Check if we already have the target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; target &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; numbers:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Found solution!&amp;#39;&lt;/span&gt;, steps_so_far)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Keep a set of the commutative operations we&amp;#39;ve tried already&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  checked_commutative_operations &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; set()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Try each of the combinations of operands with each operator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; index, number &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; enumerate(numbers):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; second_index, second_number &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; enumerate(numbers):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Though not with itself.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; index &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; second_index:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Get a list of the other numbers to pass to the recursive call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      other_numbers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; numbers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;copy()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      other_numbers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      other_numbers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(second_number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; operators:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# We can do a basic check here to make sure that&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# the operation is valid and not redundant&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; is_valid(number, operator, second_number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; is_redundant(number, operator, second_number)):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Turn it into &amp;#39;2 * 3&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        operation_string &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(number,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                           operator,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                           second_number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Do not ever do this in any code that isn&amp;#39;t&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# low-effort, throwaway blog post code.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        new_number &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(eval(operation_string))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# If it&amp;#39;s commutative, see if we&amp;#39;ve already checked that combo.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# If so, skip it. If not, add it to the set for future pairs.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; is_commutative(operator):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          canonical_form &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; get_canonical_form(number, operator, second_number)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; canonical_form &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; checked_commutative_operations:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          checked_commutative_operations&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add(canonical_form)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Recursive call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        check_combo(other_numbers &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; [new_number],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    target,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    steps_so_far &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; [operation_string])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# We&amp;#39;ll want to ensure that the operations for the given&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# operands are valid for the rules of the game.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is_valid&lt;/span&gt;(first_operand, operator, second_operand):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# No negatives allowed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; first_operand &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; second_operand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Also, integers only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; first_operand &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; second_operand &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# If we&amp;#39;re multiplying or dividing and either operand is a 1, we can just skip it.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is_redundant&lt;/span&gt;(first_operand, operator, second_operand):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ((operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; (first_operand &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; second_operand &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is_commutative&lt;/span&gt;(operator):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; operator &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 2 + 3 is the same as 3 + 2, but we can put them&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# in the same &amp;#39;smaller operator larger&amp;#39; form&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_canonical_form&lt;/span&gt;(first_operand, operator, second_operand):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(min(first_operand, second_operand),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                           operator,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                           max(first_operand, second_operand))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;check_combo([&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;], &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, [])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, we have a nice, crisp output:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Found solution! [&amp;#39;1 + 2&amp;#39;, &amp;#39;3 + 3&amp;#39;]
Found solution! [&amp;#39;1 + 3&amp;#39;, &amp;#39;2 + 4&amp;#39;]
Found solution! [&amp;#39;2 + 3&amp;#39;, &amp;#39;1 + 5&amp;#39;]
Found solution! [&amp;#39;2 * 3&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That matches what we had before, and just to check for &lt;code&gt;7&lt;/code&gt;, we can do &lt;code&gt;check_combo([1, 2, 3], 7, [])&lt;/code&gt;, which gives us the single, expected answer:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Found solution! [&amp;#39;2 * 3&amp;#39;, &amp;#39;1 + 6&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;trying-on-the-real-problem&#34;&gt;Trying on the real problem&lt;/h3&gt;
&lt;p&gt;We can try this on one of the real problems now to see how it does:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;real-example.png&#34; alt=&#34;Screenshot of one of the Digits problems, with [1, 2, 4, 5, 10, 25] and target 94&#34;&gt;&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;check_combo([1, 2, 4, 5, 10, 25], 94, [])&lt;/code&gt; results in&amp;hellip; a lot:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;monstrosity.gif&#34; alt=&#34;Screencast of an absolute cascade of results — there are many, many solutions&#34;&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s only a small subset of the answers as the repl was firing away — there are clearly a ton, even after our basic improvements.&lt;/p&gt;
&lt;p&gt;Instead of printing them, let&amp;rsquo;s do a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can count the total number of solutions&lt;/li&gt;
&lt;li&gt;We can also save them by length, and inspect just one of each&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;I won&amp;rsquo;t include all of the code here, but the changes would be like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;operators &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;all_solutions &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;total_checked &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;check_combo&lt;/span&gt;(numbers, target, steps_so_far):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;global&lt;/span&gt; total_checked
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  total_checked &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Check if we already have the target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; target &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; numbers:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# print(&amp;#39;Found solution!&amp;#39;, steps_so_far)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    all_solutions&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(steps_so_far)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ... all of the other code, then at the end:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;metrics&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  counts_by_length &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  short_solution &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; solution &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; all_solutions:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    solution_length &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; len(solution)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; solution_length &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; counts_by_length:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      counts_by_length[solution_length] &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      counts_by_length[solution_length] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Also keep around one of the shortest solutions to see later&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; short_solution &lt;span style=&#34;color:#f92672&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; solution_length &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; len(short_solution):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      short_solution &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; solution
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;We checked&amp;#39;&lt;/span&gt;, total_checked, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;subproblems&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;We have&amp;#39;&lt;/span&gt;, len(all_solutions), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;total solutions!&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;The number of solutions by number of operations:&amp;#39;&lt;/span&gt;, counts_by_length)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;One of the shortest solutions:&amp;#39;&lt;/span&gt;, short_solution)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;check_combo([&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;], &lt;span style=&#34;color:#ae81ff&#34;&gt;94&lt;/span&gt;, [])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;metrics()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running this with the metrics yields the following after a bit:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;We checked 777019 subproblems
We have 1319 total solutions!
The number of solutions by number of operations: {5: 1119, 4: 194, 3: 6}
One of the shortest solutions: [&amp;#39;1 + 5&amp;#39;, &amp;#39;4 * 25&amp;#39;, &amp;#39;100 - 6&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No wonder this took a while — we ended up checking &lt;em&gt;hundreds of thousands&lt;/em&gt; of subproblems. There were over a thousand ways to get to the target with 5 operations, around two hundred that got there with 194, and just six that got there in 3 operations. I printed out one of those &amp;ldquo;get it in 3&amp;rdquo; solutions, and it&amp;rsquo;s actually a pretty natural one for coming up by hand — usually I try to get somewhere close to the total with multiplication with the bigger numbers, then look for adding/subtracting from there.&lt;/p&gt;
&lt;p&gt;That brings up another question: why are we able to solve this as humans, with our puny little brain calculators, without trying every solution? It&amp;rsquo;s because we use strategy to avoid the full tree. Things like targeting large intermediate numbers that are close to the solution or knowing that if you&amp;rsquo;re in the thousands already from multiplying big numbers and that there&amp;rsquo;s no real point going farther than that. It&amp;rsquo;s interesting to think how we might translate some of these solutions to a programmatic approach, but I&amp;rsquo;ve left that for another day.&lt;/p&gt;
&lt;h3 id=&#34;what-does-the-difficulty-look-like&#34;&gt;What does the difficulty look like?&lt;/h3&gt;
&lt;p&gt;I ran this for the rest of today&amp;rsquo;s problems to see what the solution space looked like for each one.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;(obviously, game spoilers below)&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;checking [2, 3, 5, 10, 15, 25] 195
We checked 1250803 subproblems
We have 1844 total solutions!
The number of solutions by number of operations: {5: 1294, 4: 510, 3: 39, 2: 1}
One of the shortest solutions: [&amp;#39;3 + 10&amp;#39;, &amp;#39;15 * 13&amp;#39;]

checking [3, 5, 7, 9, 11, 20] 251
We checked 1077566 subproblems
We have 375 total solutions!
The number of solutions by number of operations: {4: 102, 5: 268, 3: 5}
One of the shortest solutions: [&amp;#39;3 * 7&amp;#39;, &amp;#39;11 * 21&amp;#39;, &amp;#39;20 + 231&amp;#39;]

checking [4, 5, 6, 9, 11, 20] 382
We checked 991830 subproblems
We have 106 total solutions!
The number of solutions by number of operations: {5: 87, 4: 19}
One of the shortest solutions: [&amp;#39;4 * 9&amp;#39;, &amp;#39;11 * 36&amp;#39;, &amp;#39;6 + 396&amp;#39;, &amp;#39;402 - 20&amp;#39;]

checking [3, 13, 19, 20, 23, 25] 456
We checked 938291 subproblems
We have 989 total solutions!
The number of solutions by number of operations: {5: 916, 4: 73}
One of the shortest solutions: [&amp;#39;3 * 13&amp;#39;, &amp;#39;19 * 23&amp;#39;, &amp;#39;39 - 20&amp;#39;, &amp;#39;437 + 19&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Overall, it seems like they&amp;rsquo;re trending fewer solutions to the right, save for the very last one. This generally makes sense if we think about our wetware approaches as well, as these big numbers also seem a little more intimidating. I&amp;rsquo;m much more familiar with my multiples below 15, and I feel like I have much more intuition for the smaller numbers. For the last problem, I think this is where more of the complicated difficulty concepts come into play. There may be more ways to get to &lt;code&gt;456&lt;/code&gt;, but 1) our starter numbers are generally much larger and 2) the larger target feels trickier to get to.&lt;/p&gt;
&lt;p&gt;Pretty neat.&lt;/p&gt;
&lt;h3 id=&#34;improving-this-further&#34;&gt;Improving this further&lt;/h3&gt;
&lt;p&gt;Even with these improvements, it&amp;rsquo;s still a brutally brute-force solution, and each problem takes ~30 seconds when I run it in the repl. We could still probably improve it further — for example, we&amp;rsquo;re definitely duplicating work, since the code above isn&amp;rsquo;t looking at commutative across steps (or even if the result of the step is used later in the solution — who cares about an intermediate number if it doesn&amp;rsquo;t show up in the rest of the steps). The counts are also definitely not a perfect reflection of difficulty, as &lt;code&gt;&#39;6 + 396&#39;, &#39;402 - 20&#39;&lt;/code&gt; is the same as both &lt;code&gt;&#39;396 - 20&#39;, &#39;376 + 6&#39;&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code&gt;&#39;20 - 6&#39;, &#39;402 - 14&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This works though, which meets my bar for fun, exploratory code. I&amp;rsquo;ll leave further improvements as an exercise for the reader, as this is good enough for now.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;Thank you again for attending our onsite interview process. Based on your interview performance, we have chosen to not move you forward in the process. In particular, interviewers mentioned your lack of interest in further optimizing your solutions and your constant mentioning of naps and your desire to go home to play with your dog.&lt;/p&gt;
&lt;p&gt;We will keep your resume on file for future openings.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can play with the code (at your own risk) at &lt;a href=&#34;https://replit.com/@lxls/JointRedundantSquare&#34;&gt;https://replit.com/@lxls/JointRedundantSquare&lt;/a&gt;, with the caveat that it&amp;rsquo;s just throwaway blog post code.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The dog hair and the computer mouse</title>
      <link>https://alexanderell.is/posts/dog-hair-mouse/</link>
      <pubDate>Thu, 13 Apr 2023 08:01:09 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/dog-hair-mouse/</guid>
      <description>&lt;p&gt;Because I spend my entire workday on the computer, my mouse is very important to me. With my basic optical mouse, I&amp;rsquo;m
far from a connoisseur, but there are a few things that I&amp;rsquo;m just so used to that I notice any changes immediately. For
example, it&amp;rsquo;s easy for me to notice changes to my mouse acceleration, and even the slightest change in acceleration
throws me off. I notice it so much that I have a program on my laptop to disable any mouse acceleration — even the default
lowest value isn&amp;rsquo;t good enough for me.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also very, very far from a hardware expert.  My physical object debugging skills include unplugging and plugging it
back in, turning it off and back on, and then trying it with different hardware if possible. This is usually enough to
solve 99% of the problems I encounter, but I do fear that other 1%.&lt;/p&gt;
&lt;p&gt;So you can imagine my dismay when one day, my computer mouse stopped working.&lt;/p&gt;
&lt;p&gt;It had been working fine all morning. I had clicked, scrolled, panned, dragged, and opened in a new tab all day without
any issue. All of a sudden, it was jittery. The cursor would jump weirdly, sometimes respecting my movements, but
sometimes jumping across the screen at random.&lt;/p&gt;
&lt;p&gt;I went through the usual steps. Unplug, plug back in. Unplug from USB hub and plug directly in. Switch out the USB-A to
USB-C adaptor. Try a different USB port. Quit the mouse acceleration program, just in case, then restart it. Check to
make sure I don&amp;rsquo;t have the mouse on a reflective surface. Try to make do for a bit without the mouse, but then struggle
when trying to effectively tab through a new Google search page. Restart the computer, just in case.&lt;/p&gt;
&lt;p&gt;After a few unsuccessful minutes of this, I happened to look at the bottom of my
mouse.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;bottom-of-mouse.png&#34; alt=&#34;Picture of the bottom of my optical mouse with a dog hair stuck in the laser&#34;&gt;&lt;/p&gt;
&lt;p&gt;There was a small dog hair&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; stuck in the little laser area. Optical mice work by shining a light on the surface below and
using a sensor to compare how the pattern it sees changes from one instant to another. If the small pattern on your
mouse pad has moved a millimeter, it&amp;rsquo;s likely that the mouse has moved a millimeter, and it can send that signal. But,
if there&amp;rsquo;s a dog hair in the way, there may be some wildly different patterns as the hair is dragged across the front of
the sensor. You may just end up with weird, jittery motion.&lt;/p&gt;
&lt;p&gt;I removed the hair, and the mouse worked fine.&lt;/p&gt;
&lt;h3 id=&#34;facing-little-problems-youve-seen-before&#34;&gt;Facing little problems you&amp;rsquo;ve seen before&lt;/h3&gt;
&lt;p&gt;This has since happened a few times, and every time, I&amp;rsquo;ve (usually correctly) checked for dog hair first. It&amp;rsquo;s top of
mind, and it&amp;rsquo;s now my first go-to thing when checking&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The other day at work, when walking a new team member through some of our systems, I found myself using the metaphor of
&amp;ldquo;scar tissue&amp;rdquo; for little problems you&amp;rsquo;ve seen and built up experience with. I think about this a lot when working with software, as we
see similar instances of small problems we&amp;rsquo;ve seen before. As you learn from seeing something weird, from making a
mistake, or from spending time debugging, you slowly build this arsenal of problems you&amp;rsquo;ve seen before. Most of
them are really just nameless echoes of time you&amp;rsquo;ve spent digging into hairy bugs or things that have burned you in the
past.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Ah, a 502? That probably means something related to security groups when talking to the upstream service, and I&amp;rsquo;d check there first.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;For that new feature, it&amp;rsquo;ll be helpful to add metrics and logging _before_ you may need them.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;The text fits in the div when it&amp;rsquo;s in English, but have you tried when the page is in German?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you ask me how I know these things, it&amp;rsquo;s because I&amp;rsquo;ve already found a hundred dog hairs in a hundred different mice. I&amp;rsquo;ll
probably find hundreds more.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;../terrier-dist-sys/real-karl.jpg&#34;&gt;Small dog tax&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;I also furminator my dog quite a bit, but to no avail. He is a scruffy, grumpy, hair-creating machine.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Things that are hard to measure, things that are easy to measure</title>
      <link>https://alexanderell.is/posts/measure/</link>
      <pubDate>Sun, 26 Feb 2023 15:29:21 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/measure/</guid>
      <description>&lt;p&gt;There are things that are hard to measure, and there are things that are easy to measure. I think about this duality a
lot when it comes to software engineering, especially when it comes to the difficult task of measuring developer
productivity and engineer effectiveness&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Taking a very naive approach, there are many easily measurable aspects of the job that you &lt;em&gt;could&lt;/em&gt; use&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jira tickets closed&lt;/li&gt;
&lt;li&gt;Number of pull requests submitted (or reviewed)&lt;/li&gt;
&lt;li&gt;Number of lines of code&lt;/li&gt;
&lt;li&gt;Number of interviews given&lt;/li&gt;
&lt;li&gt;Number of design documents written&lt;/li&gt;
&lt;li&gt;Features shipped&lt;/li&gt;
&lt;li&gt;Money coming in because of those features (if there&amp;rsquo;s a direct connection)&lt;/li&gt;
&lt;li&gt;Time engineer spends at the office&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;What about the parts of the job that are harder to measure?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The quality of those PRs, PR reviews, lines of codes, or interviews&lt;/li&gt;
&lt;li&gt;How well those features are built&lt;/li&gt;
&lt;li&gt;Mentoring and helping other engineers grow&lt;/li&gt;
&lt;li&gt;Giving technical talks and spreading knowledge&lt;/li&gt;
&lt;li&gt;Reducing technical debt&lt;/li&gt;
&lt;li&gt;Deciding to &lt;em&gt;not&lt;/em&gt; build features (when appropriate)&lt;/li&gt;
&lt;li&gt;The monetary value of engineering that&amp;rsquo;s not directly connected to revenue&lt;/li&gt;
&lt;li&gt;The knowledge accumulated by building an initial feature that you can use as a foundation for future work&lt;/li&gt;
&lt;li&gt;Even just the value of two team members just sitting down for coffee&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Some of these can be correlated to things across the divide. If you&amp;rsquo;re an effective engineer shipping new features, you
may write a fair bit of code. If you&amp;rsquo;re able to effectively build consensus about solving a difficult problem with
cross team buy-in, you may end up writing a few design documents. If you&amp;rsquo;re able to narrow down your team&amp;rsquo;s focus to
just those features the team can (and should) build, you&amp;rsquo;ll likely be able to better ship those features.&lt;/p&gt;
&lt;p&gt;But, this positive relationship doesn&amp;rsquo;t hold up in the other direction, and that&amp;rsquo;s the danger. The number of
lines of code an engineer writes is not indicative of the value of that code — we&amp;rsquo;re in the business of &lt;em&gt;elegant&lt;/em&gt;
solutions to complex problems after all. You can&amp;rsquo;t build consensus by sheer volume of documents and meetings alone,
unless you&amp;rsquo;re hoping that the other party resigns out of fatigue by the time the decision has to be made. If you do a
bunch of interviews, is it valuable if you&amp;rsquo;re a jerk in those interviews?&lt;/p&gt;
&lt;p&gt;On the other hand, does the negative relationship hold up in the other direction? Well, it&amp;rsquo;s nuanced. Maybe the project was
exploration-heavy, and that didn&amp;rsquo;t translate to many PRs. Maybe it was a critical one line fix, but it took a month of
intensive investigation to find it. Or, maybe I really do have a second remote job,
and I&amp;rsquo;ve been too busy with it this quarter to get to all of my Jira tickets&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not possible to measure the full picture by just the easily measured metrics alone. Doing so ignores the real
depth of the job.&lt;/p&gt;
&lt;p&gt;More so, if 1) you&amp;rsquo;re just using the easy ones and 2) the way you evaluate is visible to the engineers under review&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;,
they&amp;rsquo;re going to game it. How could they not? If they&amp;rsquo;re being evaluated on the number of new features they ship and
this evaluation has a direct relationship to the money they receive or their career trajectory, of course they&amp;rsquo;re going
to optimize their time and effort towards the new features. It&amp;rsquo;s your fault for hiring a bunch of smart cookies!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So how do you measure how effective an engineer is? This is where I&amp;rsquo;m going to take the easy way out — that&amp;rsquo;s well
beyond the scope of this short post, written by a lowly engineer. All that I ask is that if you&amp;rsquo;re in the position where
you&amp;rsquo;re evaluating engineers, you look beyond the easy-to-measure metrics, as there&amp;rsquo;s so much more to the work. It&amp;rsquo;s
hard, but this is the way.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Though I&amp;rsquo;m not convinced it&amp;rsquo;s a decidable problem.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Quick caveat that neither of these are exhaustive lists.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Quick caveat that I don&amp;rsquo;t have a second remote job.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;As it should be.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>In the desert, no service, and your partner&#39;s late to the rendezvous. What now?</title>
      <link>https://alexanderell.is/posts/desert/</link>
      <pubDate>Thu, 16 Feb 2023 22:50:02 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/desert/</guid>
      <description>&lt;p&gt;Let&amp;rsquo;s say, purely hypothetically, that you&amp;rsquo;re in Joshua Tree National Park. Your partner dropped you off in the morning, and you&amp;rsquo;ve
spent the day hiking and scrambling in pretty populated areas of the park. You arranged to meet your partner at 4:45PM at the place where they dropped you off.
You&amp;rsquo;re currently in possession of a cell phone (though without any service), some snacks and water left over from your
day, a notebook, and a pen. You&amp;rsquo;re 7 miles from the entrance of the park, and there&amp;rsquo;s a decent number of other visitors
around.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;re standing at the meeting spot, and you glance at your watch. It&amp;rsquo;s 5:02. What now?&lt;/p&gt;
&lt;p&gt;At that moment, you realize how much you&amp;rsquo;ve taken cell phone service for granted. You also realize you hadn&amp;rsquo;t discussed
any contingency plans in case anything goes wrong. You had let them know which rocks you&amp;rsquo;d be scrambling on, but you
hadn&amp;rsquo;t discussed worst case scenarios in case they couldn&amp;rsquo;t get to you.&lt;/p&gt;
&lt;p&gt;The good news is you have options. The bad news is that none of them are great.&lt;/p&gt;
&lt;p&gt;Do you wait indefinitely? You don&amp;rsquo;t really have enough gear to spend the night comfortably, though you&amp;rsquo;ve got plenty of
warm layers thanks to a layering habit from growing up in a colder climate. You&amp;rsquo;d also likely want to
hitchhike out before it gets too dark, especially as your likelihood of being picked up for a ride will probably drop
as it gets darker — the long beard probably won&amp;rsquo;t help. How long do you wait? Maybe another 30 minutes? If it&amp;rsquo;s
something calamitous and they&amp;rsquo;re at urgent care or something, thirty minutes won&amp;rsquo;t make much of a difference. But what
if you hitchhike but then pass them on the way out? Do you ask your kind ride-sharer to drop you off, then you walk back
to the spot? Maybe we pair that with some frantic waving?&lt;/p&gt;
&lt;p&gt;Ah, we have our notebook. Maybe we can leave a note under a rock on a little signpost here. Not exactly leaving no
trace though, and it could easily blow away (or be picked up as litter by another visitor). They&amp;rsquo;re not very big pages
though, and would they even see it? Maybe it&amp;rsquo;s worth it anyways, and when you do meet up with your partner, you can come
back and pick it up.&lt;/p&gt;
&lt;p&gt;What if you hitchhike out but miss them as you pass them? Then you&amp;rsquo;d be at the park entrance, and they&amp;rsquo;d be waiting at
the meeting point. Their not being there on time brings to mind images of a flat tire, but &lt;em&gt;you&lt;/em&gt; not being there an hour after the
meeting time hints at a broken leg. Would they try to walk out to the rocks you told them you&amp;rsquo;d be near? You remember
that you have the newly purchased family headlamp, and it&amp;rsquo;s probably not great for them to be out there with just their phone flashlights
looking for you, especially if you&amp;rsquo;re not even out there. Maybe you should leave that note.&lt;/p&gt;
&lt;p&gt;This feels like an interview problem. &amp;ldquo;What&amp;rsquo;s the best strategy to ensure you rendezvous with your partner?&amp;rdquo; The clear
answer is that you arrange a plan if things go wrong &lt;em&gt;ahead of time&lt;/em&gt; and stick to that plan, but that&amp;rsquo;s not what they
want to hear. They just want to see how you think through a problem, though it would be nice if it compiled and passed some
tests at the end.&lt;/p&gt;
&lt;p&gt;Great.&lt;/p&gt;
&lt;p&gt;It does all feel very game theory-ish, though. If I had to assign things values, I&amp;rsquo;d say that the worst case would be
them spending the night out here looking for me. Let&amp;rsquo;s call that value -99. If I had to spend the night, it wouldn&amp;rsquo;t be
terrible, but it would be uncomfortable and they&amp;rsquo;d worry about me. Let&amp;rsquo;s call that -5. Walking 7 miles in the waning
light is also unideal, especially since I don&amp;rsquo;t have &lt;em&gt;that&lt;/em&gt; much water left. Let&amp;rsquo;s say that&amp;rsquo;s an additional -3. Leaving
the note is a slight hit to the ethic, so let&amp;rsquo;s call that -0.5.&lt;/p&gt;
&lt;p&gt;So the worst case would be if I walked 7 miles, still slept out here, &lt;em&gt;and&lt;/em&gt; they missed me and spent the night searching,
for a whopping value of -107. Let&amp;rsquo;s leave that note, though it pains me. Maybe what I can do is hitchhike out to the
entrance (skipping the 7 mile walk) while keeping a very diligent eye out for their rental car — I think it was a gray Toyota Corolla — and I wave out the window or
something to catch their eye. I hope I can see their car in the evening light.&lt;/p&gt;
&lt;p&gt;If I make it to the park entrance and still haven&amp;rsquo;t seen them, at least I&amp;rsquo;ll have cell
service and can call them, and if I still can&amp;rsquo;t reach them, I&amp;rsquo;ll hope that they&amp;rsquo;re back at the rental and not
out looking for me (hopefully they saw the note in the dark). It&amp;rsquo;s getting pretty cold, and I&amp;rsquo;m not sure they would
have brought their heavier jacket to pick me up.  But what action do I think they&amp;rsquo;ll take?&lt;/p&gt;
&lt;p&gt;You glance at your watch. It&amp;rsquo;s now 5:07. As you look up, you see your partner&amp;rsquo;s car pull into the parking lot.&lt;/p&gt;
&lt;p&gt;They were just a little late.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Practice being a beginner</title>
      <link>https://alexanderell.is/posts/beginner/</link>
      <pubDate>Tue, 01 Nov 2022 20:28:03 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/beginner/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m a big fan of trying new things. Part of the process that I&amp;rsquo;ve come to enjoy
is facing and getting over my initial hesitations and reaffirming that it&amp;rsquo;s OK
to be bad at things.&lt;/p&gt;
&lt;p&gt;Trying something new for the first time can be scary. There&amp;rsquo;s an element of
vulnerability, where you&amp;rsquo;re clearly showing that you&amp;rsquo;re not good at something.
It&amp;rsquo;s like I&amp;rsquo;m going to show up at the rock climbing gym and they&amp;rsquo;re all going to
judge me for being the out of shape noob.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Who&amp;rsquo;s dad is this?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Realistically though, that&amp;rsquo;s not the case. It&amp;rsquo;s &lt;em&gt;so&lt;/em&gt; OK to not be good, and it&amp;rsquo;s
an obvious part of learning and improving. It&amp;rsquo;s even a little silly to
explicitly write that out — of course every expert was a beginner at some point!&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s not what we commonly see though, and I easily fall into this trap as
well. It&amp;rsquo;s part of the curse of social media where the highlights reign. We
generally don&amp;rsquo;t see the hours of practice that went into the minute-long video
of the virtuoso guitar player. We don&amp;rsquo;t see the many, many hours of prep and
focus that let the chess grandmaster play so well. The Beatles showed up in
storm, without a trace of their leather-jacket marathon playing Hamburg days
where they practiced their craft for hours a day.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s kind of like learning a foreign language and getting over your hesitations
about talking with a native speaker. There&amp;rsquo;s the trope of someone claiming they
speak their second language better after a few drinks — I&amp;rsquo;ve certainly heard
(and maybe claimed) this a few times. My theory is that it&amp;rsquo;s because that
feeling of hesitation and embarrassment slips away, and your mind is free to
focus on communication, not the anxiety of what the other people will think of
you if you mispronounce or get a word wrong.&lt;/p&gt;
&lt;p&gt;I think it&amp;rsquo;s a worthwhile thing to practice. I find it too easy to stick to
things that I&amp;rsquo;m already pretty good at, where I feel comfortable and skilled.
It&amp;rsquo;s like a well-traveled rut of expertise, since I can stay in my comfort zone,
maybe slinging a new coding project or taking a new class. I don&amp;rsquo;t think there&amp;rsquo;s
anything wrong with either that or building expertise, but for me, there&amp;rsquo;s so
much more out there, and there are so many things that I&amp;rsquo;ve come to enjoy after
giving them a try. This has been especially pertinent for me as I consciously
try to move away from &lt;a href=&#34;https://alexanderell.is/posts/mscs/&#34;&gt;the grind&lt;/a&gt; and a focus on work/career.&lt;/p&gt;
&lt;p&gt;These new things double as humbling exercises, too, if you ever find yourself
feeling a little too high and mighty. Go try something new. Try rock climbing,
then nurse your sore muscles the next day and appreciate how damn good the pros
are. Put on a helmet (and maybe pads, if it&amp;rsquo;s truly your first time) and try
rolling around slowly on a skateboard, then go watch
&lt;a href=&#34;https://www.youtube.com/watch?v=gizM-PuVnY0&#34;&gt;some skate videos from the 90s&lt;/a&gt; to
see what they were mastering over 30 years ago.&lt;/p&gt;
&lt;p&gt;I may not be very good yet, but I certainly have a lot of fun doing it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Learning by working on problems just outside of your reach</title>
      <link>https://alexanderell.is/posts/scoped-problems/</link>
      <pubDate>Sun, 02 Oct 2022 21:51:56 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/scoped-problems/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s a common pattern I&amp;rsquo;ve seen when learning something new, where you can
effectively move towards a larger goal by working on problems just outside of
your reach.  I did a little thinking about what that would look like and how to
find the right steps to take.&lt;/p&gt;
&lt;h4 id=&#34;circles-of-knowledge-what-i-do-and-do-not-know&#34;&gt;Circles of knowledge: what I do and do not know&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s say you have some general knowledge in some areas. I sometimes think about
knowledge and &amp;ldquo;things you know and understand&amp;rdquo; as some sort of multidimensional
blob that can grow, contract, and connect in many different ways as you learn
and practice. For the sake of easy visualization, we can think of it as a circle
of knowledge, dividing everything into two categories: things you know and
things you don&amp;rsquo;t know.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;circle-of-knowledge.png&#34; alt=&#34;Circle saying things I know and the space outside of the circle saying things I do not know&#34;&gt;&lt;/p&gt;
&lt;p&gt;My knowledge is a weird mix of things I&amp;rsquo;ve picked up over the last few decades.
There are some things I know well, like the names of all 50 states in
alphabetical order (thanks to the song), but there are many things that only
partially fall within my circle with much of their depth unknown to me.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;things-i-know.png&#34; alt=&#34;A few subjects (like math and programming) falling some within the circle and some without&#34;&gt;&lt;/p&gt;
&lt;p&gt;There are so many things I don&amp;rsquo;t know, and there are frequently things that I
want to learn. Maybe it&amp;rsquo;s something relatively small, like a new JS web API I&amp;rsquo;m
curious about, but maybe it&amp;rsquo;s something big and amorphous, like &amp;ldquo;distributed
systems&amp;rdquo;. If we think about this boundary, where the rubber of the new thing
meets the road of what I already know, we can imagine that it&amp;rsquo;s just outside
this circle.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take distributed systems, for example, where I may have already known some
parts of a few languages, some general programming concepts, and some networking
concepts. Staring me in the face is this intimidating box, &amp;ldquo;distributed
systems&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;distributed-systems.png&#34; alt=&#34;A few subjects (like math and programming) falling some within the circle and a big box, distributed systems, outside&#34;&gt;&lt;/p&gt;
&lt;p&gt;In reality, this broad &amp;ldquo;distributed systems&amp;rdquo; is made up of many things,
including concepts around distributed computation, RPCs, fault tolerance,
consensus, debugging, and many things off the screen to the bottom.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;boundary-for-distributed-systems.png&#34; alt=&#34;A few programming things I know and many distributed systems concepts in the unknown&#34;&gt;&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t learn the big box at once. You work your way towards the goal, slowly
expanding your new beachhead of knowledge by working problems always just
outside of your reach. Much like
&lt;a href=&#34;https://matt.might.net/articles/phd-school-in-pictures/&#34;&gt;the race to the edge of human knowledge in the illustrated guide to a Ph.D.&lt;/a&gt;, it&amp;rsquo;s like
continuously adding a single additional piece of unknown as you take steps
towards the goal. Maybe you start with MapReduce to learn more about distributed
computation and ideas around splitting up work.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;map-reduce.png&#34; alt=&#34;The circle expanded to include the first concept, map reduce&#34;&gt;&lt;/p&gt;
&lt;p&gt;Suddenly, your pretty circle of knowledge has a blemish where you&amp;rsquo;ve expanded
out into the unknown. The border of the known has shifted a little, and picking
off RPCs grows it even further:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;rpcs.png&#34; alt=&#34;The circle expanded to include the second concept as well, RPCs&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bumpy! You&amp;rsquo;ve picked up a few more things, the bumpy surface of your
understanding now including a few more things, and you&amp;rsquo;re slowly building
towards that broader goal. What you learn builds off of what you&amp;rsquo;ve already
learned, and you grow your knowledge by picking off concepts and ideas.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;expanded-knowledge.png&#34; alt=&#34;The circle expanded to include most of the rest of the concepts&#34;&gt;&lt;/p&gt;
&lt;p&gt;And just like that, suddenly you&amp;rsquo;ve got pretty good coverage across this broad
area. Of course, it isn&amp;rsquo;t a slowly expanding circle, and these things rarely
are fully in your circle of knowledge. It&amp;rsquo;s some sort of amorphous blob of
connections, strengthened understanding, and jutting things you don&amp;rsquo;t know.
Everything relates to everything else, especially in programming, and everything
is so complex.&lt;/p&gt;
&lt;p&gt;The concepts above are also pretty interchangeable in their order, but that&amp;rsquo;s
not always the case for working your way through. As an exercise left to the
reader, think about what this would look like for learning a foreign language,
ramping up a new engineer on a code base, or learning a new programming
language.&lt;/p&gt;
&lt;h4 id=&#34;working-on-problems-just-outside-of-reach&#34;&gt;Working on problems just outside of reach&lt;/h4&gt;
&lt;p&gt;One of the most helpful things about this is that it gives more immediate and
appropriately scoped goals; as a junior engineer new to a codebase, I shouldn&amp;rsquo;t
be trying to learn the whole codebase in one go, but instead I should learn
parts of it by fixing bugs and adding features with the scope increasing as I
understand more and more. Someone new to programming shouldn&amp;rsquo;t be setting out to
&amp;ldquo;learn programming&amp;rdquo;, but should instead start with small programs, like printing
the sum of the first ten integers, then spend the next &lt;a href=&#34;https://norvig.com/21-days.html&#34;&gt;ten years&lt;/a&gt; working on more complicated projects. I
find it also helps to give a guided path towards these goals, especially if
you&amp;rsquo;re able to start your first steps with fundamentals that later steps will
build off of, much like you probably spent a lot of time doing basic arithmetic before learning algebra.&lt;/p&gt;
&lt;p&gt;How do you find those right steps to take? This is where guidance from a third
party can be tremendously helpful, as appropriate scoping for steps can really
help with your movement towards the eventual goal.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; Books are nice, as they
frequently give a path and steps for you to follow. Better yet is if you have
someone that knows the path well and is able to guide you along it, crafting
those steps for your current knowledge and checking to see where you are. This
is where classes are great, as they (hopefully) offer a nice balance between a
well-trod path through a subject and a helping hand from the professor and/or
TAs to guide you through it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s one of the things I really enjoy about mentorship, where you can help
create these paths and guide people along them. It&amp;rsquo;s one of the things I most
enjoyed about my previous life working as a tutor, long before I got into tech.
It was a game of figuring out where the student&amp;rsquo;s understanding was and what the
best way to get them to the next step would be. It would be a constant game of
empathy and translation, both seeing where they were and exploring the best way
to map the subject material to something that they would understand and be able
to explore themselves. This is where I think the personal touch really matters,
as the single path presented in the textbook may not be the one that makes the
most sense to your brain and where your current understanding is.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a very tricky problem, but it&amp;rsquo;s a wonderful one to solve.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Outside the scope of this section is &lt;em&gt;the best way to learn new things&lt;/em&gt;.
I&amp;rsquo;d recommend a mix of reading, building, experimentation, and talking to
smart people. It&amp;rsquo;s really up to you to discover what works best for
yourself!&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Of course, you can always figure it out by yourself as you go. I&amp;rsquo;d
recommend at least some guidance from someone with experience on the path,
since it can be so hard without knowing what you don&amp;rsquo;t know.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>iPhones and action discoverability, or &#34;How the hell was I supposed to know that?&#34;</title>
      <link>https://alexanderell.is/posts/iphone-discoverability/</link>
      <pubDate>Sat, 24 Sep 2022 11:26:15 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/iphone-discoverability/</guid>
      <description>&lt;h1 id=&#34;discoverability&#34;&gt;Discoverability&lt;/h1&gt;
&lt;p&gt;I spend a lot of my time thinking about UX and design, and I try to consciously
think about it in the wild when I can. One of the things that always catches my
eyes is the idea of discoverability, or how a user is able to discover that
action X does some thing Y.&lt;/p&gt;
&lt;p&gt;A basic way to do so is to rely on patterns that we&amp;rsquo;re all familiar with. For example,
&lt;a href=&#34;#it-doesn&#39;t-do-anything-but-still&#34;&gt;this link is blue, and if you hover over it with your mouse, you can see the cursor change&lt;/a&gt;.
That is a helpful context clue, since you&amp;rsquo;ve probably seen and interacted with
many links before, and because it has the same pattern, you can expect it to do the same
thing.&lt;/p&gt;
&lt;p&gt;Sometimes there are things that are new to you but are still intuitive when you
first explore them, like using a touch screen for the first time. We touch
things all the time, and when you first used a good touch screen, you were
probably able to quickly understand how it worked. You moved your finger, the
page scrolled, and that feedback helped you to quickly solidify the connection
between your gestures and the screen&amp;rsquo;s actions. This is like discoverability
through action, where you&amp;rsquo;re able to figure it out as you try it.&lt;/p&gt;
&lt;p&gt;I also think about the category of things that aren&amp;rsquo;t intuitive and require some
guidance, like Googling steps to show a cursor in an iOS screencast or putting
together IKEA furniture. Woe is the fool who tries to discover a complicated
IKEA piece through action! (Side note: there should be competitive IKEA furniture
speed runs). This discoverability is aided by a guide, and although the quality
of these guides can vary, sometimes it &lt;em&gt;is&lt;/em&gt; helpful to first RTFM.&lt;/p&gt;
&lt;h2 id=&#34;what-ios-is-doing-these-days&#34;&gt;What iOS is doing these days&lt;/h2&gt;
&lt;p&gt;This brings us to a weird area: what about things that are unintuitive and
unguided? Or, as I like to say, how the hell was I supposed to know it would do
that?&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to present the following features for your consideration, with an
exaggerated pointer to show the interactive actions:&lt;/p&gt;
&lt;h3 id=&#34;deleting-a-number-on-the-ios-calculator&#34;&gt;Deleting a number on the iOS calculator&lt;/h3&gt;
&lt;p&gt;To delete the last digit from a number you entered in the iOS calculator, you
can &lt;strong&gt;swipe the numbers to delete&lt;/strong&gt;.&lt;/p&gt;
&lt;div style=&#34;max-width: 400px; margin: 0 auto&#34;&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;calculator.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;calculator.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h3 id=&#34;using-the-spacebar-as-textbox-navigation&#34;&gt;Using the spacebar as textbox navigation&lt;/h3&gt;
&lt;p&gt;To better navigate around a text document, &lt;strong&gt;you can long press the spacebar,
then navigate around&lt;/strong&gt;.&lt;/p&gt;
&lt;div style=&#34;max-width: 400px; margin: 0 auto&#34;&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;space-navigate.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;space-navigate.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h3 id=&#34;switching-between-safari-pages-by-swiping-the-url-bar&#34;&gt;Switching between Safari pages by swiping the URL bar&lt;/h3&gt;
&lt;p&gt;To go back and forward in Safari, you can swipe the edges of the screen. To
switch between tabs, you can &lt;strong&gt;swipe the URL bar left and right&lt;/strong&gt;.&lt;/p&gt;
&lt;div style=&#34;max-width: 400px; margin: 0 auto&#34;&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;swipe-url-bar.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;swipe-url-bar.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h3 id=&#34;going-back-after-opening-a-new-safari-tab&#34;&gt;Going back after opening a new Safari tab&lt;/h3&gt;
&lt;p&gt;This is more of an unintuitive nitpick than a feature, but &lt;strong&gt;when you swipe back
from a new tab, you silently close that tab and return to the old tab&lt;/strong&gt;, unless
you&amp;rsquo;ve already closed the old tab.&lt;/p&gt;
&lt;div style=&#34;max-width: 400px; margin: 0 auto&#34;&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;new-tab-back.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;new-tab-back.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2 id=&#34;undiscoverability&#34;&gt;Undiscoverability&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t think any of these are intuitive or easily discoverable.  You&amp;rsquo;ll
sometimes see these features pop up in life hack or &amp;ldquo;I never knew this hidden
feature existed&amp;rdquo; tweets, and that novelty and surprise is indicative of the lack
of discoverability for these features. They&amp;rsquo;re at an odd intersection of 1) not
following previous expected patterns, 2) not being intuitive, and 3) not being
clearly explained anywhere, unless you happen to stumble upon them.&lt;/p&gt;
&lt;p&gt;This is where you frequently see some cliché comments like &amp;ldquo;Steve Jobs would
fire the people who made these&amp;rdquo;. Yeah maybe, but who am I to make that claim,
much less to argue that it would be a good thing.&lt;/p&gt;
&lt;p&gt;Good design for very complicated systems is hard, and these are just a few
points where it fell short. It ends up being a double edged sword as well,
because the complicated features (that must have taken many hours to build,
debug, test, and launch) are hard to discover — how many users end up swiping
their calculator or using the spacebar navigation? I&amp;rsquo;d love to see those
metrics.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;As much as I like to point out design shortcomings, it&amp;rsquo;s always funny to me to
think about the number of very, very complicated things we take for granted now,
with things only popping up as notable when they aren&amp;rsquo;t perfect.  It&amp;rsquo;s like
modern front ends — yes it&amp;rsquo;s complicated, but the expectations are very high as
well.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>The hobby buyer&#39;s fallacy: &#34;I&#39;d really pursue my hobby if I just bought X&#34;</title>
      <link>https://alexanderell.is/posts/buyer-fallacy/</link>
      <pubDate>Sat, 24 Sep 2022 10:13:28 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/buyer-fallacy/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s a trap I used to fall into that has happened more times than I&amp;rsquo;d like to
admit&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. The typical scenario would be something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There&amp;rsquo;s a hobby I&amp;rsquo;m into (or starting to get into)&lt;/li&gt;
&lt;li&gt;I find myself wanting to do that hobby more than I currently am&lt;/li&gt;
&lt;li&gt;I find some &lt;code&gt;THING&lt;/code&gt; that seems like it would help&lt;/li&gt;
&lt;li&gt;I somehow convince myself that having that &lt;code&gt;THING&lt;/code&gt; would make the difference&lt;/li&gt;
&lt;li&gt;I purchase that &lt;code&gt;THING&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;THING&lt;/code&gt; only slightly helps, and I may or may not do the hobby more&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Much like the scroll in Kung Fu Panda, the secret was really inside myself the
whole time. But, as I mentioned, I&amp;rsquo;ve fallen into this a few times before:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;If I only had the perfect notebooks, I would write much more by hand&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;If I had that drawing board, it would make it that much easier to practice
drawing and I would draw so much more&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;The only thing standing between me and painting is those new paint pens&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As you can imagine, the new shiny things were &lt;em&gt;not&lt;/em&gt; the only things standing
between me and doing my hobbies.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s so tempting, though. Maybe that thing is what the pros use, and of course
it would make sense to upgrade my equipment if I want to get better (spoiler:
&lt;a href=&#34;https://www.youtube.com/watch?v=zezwd8mNOnE&#34;&gt;it isn&amp;rsquo;t the shoes&lt;/a&gt;). Maybe I&amp;rsquo;ve
been more tired and burnt out recently without the energy to sit down and do my
hobbies and I fool myself into thinking this new drawing board would make the
difference. Maybe it&amp;rsquo;s only $40 and Amazon will deliver it tomorrow.&lt;/p&gt;
&lt;p&gt;At some point I started to see the pattern, and I started fooling myself less
and less and built up defenses as I became more aware of it. I have a &amp;ldquo;nice to
have&amp;rdquo; shopping list I add to when I have that spur-of-the-moment feeling — if it
would really be helpful, it&amp;rsquo;ll still be there in a month. I think about whether
I&amp;rsquo;m fooling myself with this old pattern, and having fallen into the trap a few
times, I find it easier to take a moment to think about whether I&amp;rsquo;m starting to
think like this. Even recognizing the pattern has helped me avoid it, like I&amp;rsquo;ve
given it a name and can now point to it, as if it was a gremlin in the back of
my head whispering foolish thoughts.&lt;/p&gt;
&lt;p&gt;Maybe the gremlin&amp;rsquo;s name is Purchasa. No, Purchasa, the more expensive guitar
won&amp;rsquo;t make me play more or play better.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also accepted that I won&amp;rsquo;t be 100% into all of my hobbies at the same time,
and it&amp;rsquo;s OK if my interest in something ebbs in favor of other things. If I&amp;rsquo;m
not pursuing &lt;em&gt;any&lt;/em&gt; of my hobbies, there&amp;rsquo;s probably something else going on that
I need to take a look at, but if it&amp;rsquo;s just that I&amp;rsquo;ve been playing more guitar
than piano recently, that&amp;rsquo;s totally fine. There just isn&amp;rsquo;t enough time for
everything.&lt;/p&gt;
&lt;p&gt;For new hobbies, there are little gut checks that help as well, like &amp;ldquo;try
without it for a month and see if you&amp;rsquo;re still doing your new hobby, then
re-decide from there&amp;rdquo;. Maybe we buy a single month pass at the climbing gym to
make sure we really like it before we buy the full year. Maybe the one-month
pass is a little more expensive, but it&amp;rsquo;s like a &amp;ldquo;don&amp;rsquo;t fool yourself&amp;rdquo; fee that
frequently ends up more than worth it.&lt;/p&gt;
&lt;p&gt;There are a few things that don&amp;rsquo;t fall under this, though. You really do need a
skateboard to skateboard, though bonus points if you can borrow from a friend.
If you&amp;rsquo;re going to be spending a lot of time in front of your computer for your
hobby, a good ergonomic setup is worth it. There are points at which an
equipment upgrade actually makes a real difference, but when I&amp;rsquo;m honest with
myself, I&amp;rsquo;m usually far from that point. Until we get there, let&amp;rsquo;s stick with
what we&amp;rsquo;ve got.&lt;/p&gt;
&lt;p&gt;I still need practice to get to Carnegie Hall, and buying that thing probably
won&amp;rsquo;t be the difference.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Quick caveat that this is by far one of the most first world problems to
have — I&amp;rsquo;m lucky to be blessed with such cushy problems.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Stubbornness and programming</title>
      <link>https://alexanderell.is/posts/stubborn/</link>
      <pubDate>Sun, 11 Sep 2022 16:28:14 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/stubborn/</guid>
      <description>&lt;p&gt;Every now and then, there will be the type of problem to solve where I know it
will require an extra cup of coffee, a long block of dedicated time, and a lot
of careful reading, debugging, note taking, and going for walks to think.&lt;/p&gt;
&lt;p&gt;This is often &amp;ldquo;subtle bugs that are hard to repro but we need to fix&amp;rdquo;
territory; it&amp;rsquo;s not fun greenfield development with a lot of brand new code
(which, of course, would be a problem-free fresh start&amp;hellip;), but instead,
probably a small PR with only a few lines changed that completely undersells the
deep investigation required.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s like the old joke: $5 for the hammer and $95 for knowing where to hit the
machine with the hammer.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s some sort of stubbornness that helps with these. Not stubborn like &amp;ldquo;I&amp;rsquo;m
always right and won&amp;rsquo;t listen to your opinion&amp;rdquo;, but stubborn like &amp;ldquo;it&amp;rsquo;s hour 3
of debugging and I&amp;rsquo;m slowly getting closer and I still want to keep digging
until I find that needle in the haystack&amp;rdquo;. Persistence, maybe? Focus?
Perseverance? Motivation from a quick glance at your paycheck? Whatever it is,
it&amp;rsquo;s some amount of ability to stick with it and keep digging — whatever gets
you slowly closer to a solution, until finally you shine some light on that
small subtlety that was out of place.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the computers that are the obstinate ones, inflexibly doing &lt;em&gt;exactly&lt;/em&gt; what
you tell them to do. They&amp;rsquo;re the ones not bending; in order to work together,
it&amp;rsquo;s our job to take our human minds and make ourselves think the way the
computers think. It&amp;rsquo;s that weird layer between our abstract and the computer&amp;rsquo;s
specificity, mentally translating what &lt;em&gt;should happen&lt;/em&gt; into those careful
instructions for the machine to follow.&lt;/p&gt;
&lt;p&gt;Very rude of them, when you think about it.&lt;/p&gt;
&lt;p&gt;This kind of perseverance can be very helpful when working with computers. You
build it up naturally over time, starting with your own little programs, then
larger ones, then ones other people wrote, then maybe even ones other people
wrote over many years and ones that work across many computers. You can get
better at it too, much like anything else, with practice, focus on the process,
and some metacognition as you think about how your brain works as you go through
the process. At the risk of recommending Julia Evans&amp;rsquo; zines and site in every
post, I highly recommend her
&lt;a href=&#34;https://jvns.ca/blog/2022/08/30/a-way-to-categorize-debugging-skills/&#34;&gt;&lt;em&gt;Some ways to get better at debugging&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I wonder if there&amp;rsquo;s some room for more focused learning around debugging.
Imagine something like a class-like program where you&amp;rsquo;re given a code base that
has a number of bugs in it. Much like a developer ramping up on a project, you
could go through the ramp-up process, starting by fixing a few trivial starter
bugs, then working on larger and larger bugs. One of the things I didn&amp;rsquo;t see too
much of in the CS classes I&amp;rsquo;ve taken is the experience of working through other
peoples&amp;rsquo; code, and I wonder if something like this would be a good way to build
those skills (or at least to give some preview of the nitty gritty you&amp;rsquo;ll get to
deal with on the job).&lt;/p&gt;
&lt;p&gt;Another interesting shift I&amp;rsquo;ve seen recently is some amount of focus on
debugging and working with realistic scenarios as part of the job interview
process. It seems like it&amp;rsquo;s much harder to quantify, but given that it&amp;rsquo;s such a
big part of what we do, it&amp;rsquo;s an interesting area to explore.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Writing a toy WebSocket server from scratch</title>
      <link>https://alexanderell.is/posts/websockets-from-scratch/</link>
      <pubDate>Sun, 28 Aug 2022 12:06:08 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/websockets-from-scratch/</guid>
      <description>&lt;p&gt;WebSocket (WS) connections are pretty neat. I&amp;rsquo;ve gotten to play around with them
before, both for personal and professional projects, but my view has mostly been
at a high level and concerned mainly with the things that I can build on top of
them. I haven&amp;rsquo;t spent much time digging through the protocol.&lt;/p&gt;
&lt;p&gt;I thought it would be pretty interesting to build a toy WS server from scratch.
Like many other projects, I find building a toy system to be such a good way to
learn more, and I&amp;rsquo;ll be talking about this toy WS server (and some of those
things I learned) in the rest of the post.&lt;/p&gt;
&lt;h1 id=&#34;websocket&#34;&gt;WebSocket&lt;/h1&gt;
&lt;p&gt;The main idea behind the WebSocket protocol is bi-directional communication.
Rather than the usual request/response lifecycle, with a client asking a server
for resources, WebSockets allow for messages to be sent in either direction.
Think of a chat application, where the client should be notified by the server
that there&amp;rsquo;s a new message for them. You could have the client long poll,
continuously asking the server if there&amp;rsquo;s anything new, but with a WebSocket
connection, the server can independently send those new messages to the client,
allowing the client to passively receive when it needs to.&lt;/p&gt;
&lt;h1 id=&#34;the-websocket-connection-flow&#34;&gt;The WebSocket connection flow&lt;/h1&gt;
&lt;p&gt;WebSocket connections are initiated via a regular HTTP request from the client
that indicates it would like to switch, or upgrade, to talking via a WebSocket
connection. It&amp;rsquo;s &lt;em&gt;kind of&lt;/em&gt; like sending someone a Slack message asking if they
want to hop on a quick video call — it&amp;rsquo;s still communicating over the computer,
and you&amp;rsquo;re using one method to initiate the next.&lt;/p&gt;
&lt;p&gt;The steps to the flow are the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client sends an HTTP request to the server indicating it would like to switch
to WS&lt;/li&gt;
&lt;li&gt;Server responds saying it&amp;rsquo;s OK to switch to WS&lt;/li&gt;
&lt;li&gt;Client and server can start sending WS frames back and forth&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;basic-handshake.png&#34; alt=&#34;Sequence diagram showing the basic WS handshake&#34;&gt;&lt;/p&gt;
&lt;!---
participant &#34;Client&#34; as C
participant &#34;Server&#34; as S

C-&gt;S: HTTP: &#34;I would like to talk WebSocket with you!&#34;

S-&gt;C: HTTP: &#34;WebSocket sounds good to me!&#34;

C-&gt;S: WebSocket: &#34;data&#34;

S-&gt;C: WebSocket: &#34;some other data&#34;

S-&gt;C: WebSocket: &#34;some other data initiated by the server&#34;
--&gt;
&lt;p&gt;The real steps are of course a little more complicated, including the origin,
what and how to upgrade, protocols, extensions, and a few other pieces of data.
A typical handshake request may look like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And a typical response:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&amp;rsquo;re interested in more details, I&amp;rsquo;d &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc6455&#34;&gt;recommend reading through RFC6455&lt;/a&gt; — it&amp;rsquo;s not too bad to dig through!&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s this &lt;code&gt;Sec-WebSocket-*&lt;/code&gt; business? It&amp;rsquo;s one of the ways that the server can
prove to the client that it received the handshake and it&amp;rsquo;s responding to that
request. The server does so by hashing the key with a well-known magic string,
&lt;code&gt;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&lt;/code&gt;, then base64 encoding it and sending it
back as the handshake response. &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc6455#section-1.3&#34;&gt;If you&amp;rsquo;re curious, you can see section 1.3 for more details,&lt;/a&gt;
and I&amp;rsquo;ll have an example in the later code as well.&lt;/p&gt;
&lt;p&gt;This means the full conversation looks a little more like this:&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;detailed-handshake.png&#34; alt=&#34;Sequence diagram showing the HTTP messages being sent back and forth&#34;&gt;&lt;/p&gt;
&lt;!---
participant &#34;Client&#34; as C
participant &#34;Server&#34; as S

C-&gt;S: GET /websocket HTTP/1.1\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: key

note over S: Hash key with magic string and encode.\nMaybe decide on extension/protocol

S-&gt;C: HTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: modified-key

note over C: Start sending WS frames

C-&gt;S: WebSocket frames as binary

S-&gt;C: WebSocket frames as binary

S-&gt;C: WebSocket frames as binary
--&gt;
&lt;h1 id=&#34;a-toy-websocket-server&#34;&gt;A toy websocket server&lt;/h1&gt;
&lt;p&gt;I started with a basic idea: what&amp;rsquo;s the smallest server we can build that can
handle an incoming websocket message? For example, what would it take to handle
the following, which is the most basic way to initiate WS communication via
JavaScript:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const exampleWS = new WebSocket(&amp;#34;ws://localhost:5006/websocket&amp;#34;);
exampleWS.send(&amp;#34;foo&amp;#34;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These two little lines are deceiving; there&amp;rsquo;s a lot going on under the hood.&lt;/p&gt;
&lt;p&gt;First, for this toy project, it&amp;rsquo;s helpful for myself to define two things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Goal&lt;/strong&gt;: write a basic server that can handle the WS handshake and parse an incoming WS frame to see the message sent from the client&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Non-goals&lt;/strong&gt;: writing a robust or real HTTP server, writing a fully compliant WS server, or handling all edge cases&lt;/p&gt;
&lt;p&gt;With that in mind, the steps will look something along these lines:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a very basic HTTP server to handle incoming HTTP requests&lt;/li&gt;
&lt;li&gt;Handle the initial handshake request and the connection upgrade&lt;/li&gt;
&lt;li&gt;Actually parse the incoming WS frames&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;h2 id=&#34;starting-with-http&#34;&gt;Starting with HTTP&lt;/h2&gt;
&lt;p&gt;We can start by writing a very basic HTTP server that can handle incoming
requests. This example is very much inspired by Beej&amp;rsquo;s Guide to Network
Programming&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, and the flow is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Have one socket that listens for incoming connections&lt;/li&gt;
&lt;li&gt;When there&amp;rsquo;s a new connection on it, create a new socket for listening to it&lt;/li&gt;
&lt;li&gt;When the new sockets are ready to read from, handle the incoming requests&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;This isn&amp;rsquo;t very exciting, but we can roll a very rough server in a big handful
of lines:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Very basic HTTP server that just listens on a port and responds.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; socket
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; select
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TCP_IP &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TCP_PORT &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5006&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BUFFER_SIZE &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1024&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DEFAULT_HTTP_RESPONSE &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&amp;lt;meta http-equiv=&amp;#34;content-type&amp;#34; content=&amp;#34;text/html;charset=utf-8&amp;#34;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;TITLE&amp;gt;200 OK&amp;lt;/TITLE&amp;gt;&amp;lt;/HEAD&amp;gt;&amp;lt;BODY&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;H1&amp;gt;200 OK&amp;lt;/H1&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Welcome to the default.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;/BODY&amp;gt;&amp;lt;/HTML&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    Creates the front-door TCP socket and listens for connections.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# We can have a main socket that listens for initial connections.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;socket(socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;AF_INET, socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SOCK_STREAM)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;setsockopt(socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SOL_SOCKET, socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SO_REUSEADDR, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bind((TCP_IP, TCP_PORT))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;listen(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Listening on port: &amp;#39;&lt;/span&gt;, TCP_PORT)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    input_sockets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [tcp_socket]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    output_sockets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []  &lt;span style=&#34;color:#75715e&#34;&gt;# Maybe use, we&amp;#39;ll see&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    xlist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []  &lt;span style=&#34;color:#75715e&#34;&gt;# Not using&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Get the sockets that are ready to read (the first of the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# three-tuple).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        readable_sockets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; select&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;select(input_sockets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         output_sockets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         xlist,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; ready_socket &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; readable_sockets:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Make sure it&amp;#39;s not already closed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (ready_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fileno() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ready_socket &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; tcp_socket:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Handling main door socket&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                handle_new_connection(tcp_socket, input_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Handling regular socket read&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                handle_request(ready_socket, input_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle_new_connection&lt;/span&gt;(main_door_socket, input_sockets):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# When we get a connection on the main socket, we want to accept a new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# connection and add it to our input socket list. When we loop back around,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# that socket will be ready to read from.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    client_socket, client_addr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; main_door_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;accept()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;New socket&amp;#39;&lt;/span&gt;, client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fileno(), &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;from address:&amp;#39;&lt;/span&gt;, client_addr)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    input_sockets&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(client_socket)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle_request&lt;/span&gt;(client_socket, input_sockets):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Handling request from client socket:&amp;#39;&lt;/span&gt;, client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fileno())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Very naive approach: read until we find the last blank line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        data_in_bytes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;recv(BUFFER_SIZE)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Connection on client side has closed.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(data_in_bytes) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            close_socket(client_socket, input_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            input_sockets&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(client_socket)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        message_segment &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_in_bytes&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        message &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; message_segment
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (len(message) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; message_segment[&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;:] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Received message:&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (method, target, http_version, headers_map) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parse_request(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;method, target, http_version:&amp;#39;&lt;/span&gt;, method, target, http_version)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;headers:&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(headers_map)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# For now, just return a 200. Should probably return length too, eh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send(&lt;span style=&#34;color:#e6db74&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HTTP/1.1 200 OK&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; DEFAULT_HTTP_RESPONSE)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    close_socket(client_socket, input_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Parses the first line and headers from the request.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_request&lt;/span&gt;(request):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers_map &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Assume headers and body are split by &amp;#39;\r\n\r\n&amp;#39; and we always have them.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Also assume all headers end with&amp;#39;\r\n&amp;#39;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Also assume it starts with the method.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    split_request &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    [method, target, http_version] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; split_request[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; split_request[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; header_entry &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; headers:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        [header, value] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; header_entry&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;: &amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Headers are case insensitive, so we can just keep track in lowercase.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        headers_map[header&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lower()] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; value
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (method, target, http_version, headers_map)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;close_socket&lt;/span&gt;(client_socket, input_sockets):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    input_sockets&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(client_socket)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Not very robust, but life is short and toy projects are for fun! What does that look like from the client side?&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;~: $ curl -v 127.0.0.1:5006
*   Trying 127.0.0.1:5006...
* Connected to 127.0.0.1 (127.0.0.1) port 5006 (#0)
&amp;gt; GET / HTTP/1.1
&amp;gt; Host: 127.0.0.1:5006
&amp;gt; User-Agent: curl/7.77.0
&amp;gt; Accept: */*
&amp;gt;
* Mark bundle as not supporting multiuse
&amp;lt; HTTP/1.1 200 OK
* no chunk, no close, no size. Assume close to signal end
&amp;lt;
&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&amp;lt;meta http-equiv=&amp;#34;content-type&amp;#34; content=&amp;#34;text/html;charset=utf-8&amp;#34;&amp;gt;
&amp;lt;TITLE&amp;gt;200 OK&amp;lt;/TITLE&amp;gt;&amp;lt;/HEAD&amp;gt;&amp;lt;BODY&amp;gt;
&amp;lt;H1&amp;gt;200 OK&amp;lt;/H1&amp;gt;
Welcome to the default.
&amp;lt;/BODY&amp;gt;&amp;lt;/HTML&amp;gt;

* Closing connection 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice. And on the server?&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;websockets-from-scratch: $ python3 foo.py
Listening on port:  5006
Handling main door socket
New socket 4 from address: (&amp;#39;127.0.0.1&amp;#39;, 60294)
Handling regular socket read
Handling request from client socket: 4
Received message:
GET / HTTP/1.1
Host: 127.0.0.1:5006
User-Agent: curl/7.77.0
Accept: */*


method, target, http_version: GET / HTTP/1.1
headers:
{&amp;#39;host&amp;#39;: &amp;#39;127.0.0.1:5006&amp;#39;, &amp;#39;user-agent&amp;#39;: &amp;#39;curl/7.77.0&amp;#39;, &amp;#39;accept&amp;#39;: &amp;#39;*/*&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice! This is basically all the HTTP we need, since we&amp;rsquo;ll be responding to a
basic GET request and parsing the headers, so no need to get fancier.&lt;/p&gt;
&lt;h2 id=&#34;handling-the-initial-handshake-request&#34;&gt;Handling the initial handshake request&lt;/h2&gt;
&lt;p&gt;As I mentioned before, the initial handshake includes a few indicators that the
client would like to switch over to talking over WebSocket. We can do a few
basic checks to see if an incoming request is something along those lines,
starting with a check to see if it&amp;rsquo;s a valid request:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;is_valid_ws_handshake_request&lt;/span&gt;(method, target, http_version, headers_map):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# There are a few things to verify to see if it&amp;#39;s a valid WS handshake.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# First, the method must be get.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    is_get &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; method &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# HTTP version must be &amp;gt;= 1.1. We can do a really naive check.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    http_version_number &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; float(http_version&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;)[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    http_version_enough &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; http_version_number &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Finally, we should have the right headers. This is a subset of what we&amp;#39;d&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# really want to check, but it works for now!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    headers_valid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;upgrade&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; headers_map &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         headers_map&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;upgrade&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;websocket&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;connection&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; headers_map &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         headers_map&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;connection&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Upgrade&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;sec-websocket-key&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; headers_map)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (is_get &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; http_version_enough &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; headers_valid)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If it is a valid handshake request, we&amp;rsquo;ll need to do that trick with the
incoming &lt;code&gt;Sec-WebSocket-Key&lt;/code&gt; header, where we concatenate it with the Magic
String, sha1 hash it, and base64 encode it before returning it as the
&lt;code&gt;Sec-WebSocket-Accept&lt;/code&gt; header.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MAGIC_WEBSOCKET_UUID_STRING &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;generate_sec_websocket_accept&lt;/span&gt;(sec_websocket_key):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# We generate the accept key by concatenating the sec-websocket-key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# and the magic string, Sha1 hashing it, and base64 encoding it.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# See https://datatracker.ietf.org/doc/html/rfc6455#page-7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    combined &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sec_websocket_key &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; MAGIC_WEBSOCKET_UUID_STRING
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hashed_combined_string &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; hashlib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sha1(combined&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;encode())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    encoded &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; base64&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;b64encode(hashed_combined_string&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;digest())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; encoded
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, we just need to tie this together with a check on the target and a handler for the handshake that returns the appropriate response:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WS_ENDPOINT &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/websocket&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle_request&lt;/span&gt;(client_socket, input_sockets, ws_sockets):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Handling request from client socket:&amp;#39;&lt;/span&gt;, client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fileno())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Very naive approach: read until we find the last blank line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        data_in_bytes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;recv(BUFFER_SIZE)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Connection on client side has closed.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(data_in_bytes) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            close_socket(client_socket, input_sockets, ws_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        message_segment &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_in_bytes&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        message &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; message_segment
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (len(message) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; message_segment[&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;:] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Received message:&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (method, target, http_version, headers_map) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parse_request(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;method, target, http_version:&amp;#39;&lt;/span&gt;, method, target, http_version)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;headers:&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(headers_map)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# We will know it&amp;#39;s a websockets request if the handshake request is&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# present.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; target &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; WS_ENDPOINT:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;request to ws endpoint!&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; is_valid_ws_handshake_request(method,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         target,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         http_version,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         headers_map):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            handle_ws_handshake_request(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                client_socket,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ws_sockets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                headers_map)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Invalid WS request.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send(&lt;span style=&#34;color:#e6db74&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HTTP/1.1 400 Bad Request&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            close_socket(client_socket, input_sockets, ws_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle_ws_handshake_request&lt;/span&gt;(client_socket,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                ws_sockets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                headers_map):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Save this socket in the WS sockets list so we will know to speak WS with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# it in the future.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ws_sockets&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(client_socket)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# To handle a WS handshake, we have to generate an accept key from the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# sec-websocket-key and a magic string.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sec_websocket_accept_value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; generate_sec_websocket_accept(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        headers_map&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;sec-websocket-key&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# We can now build the response, telling the client we&amp;#39;re switching&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# protocols while providing the key.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_response &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HTTP/1.1 101 Switching Protocols&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_response &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Upgrade: websocket&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_response &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Connection: Upgrade&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_response &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Sec-WebSocket-Accept: &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; sec_websocket_accept_value&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_response &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;response:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;,websocket_response)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;send(websocket_response&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;encode())
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, when we&amp;rsquo;re checking a socket that&amp;rsquo;s read to read from, we can see if
it&amp;rsquo;s a socket we&amp;rsquo;re chatting WS with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    Creates the front-door TCP socket and listens for connections.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# We can have a main socket that listens for initial connections.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;socket(socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;AF_INET, socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SOCK_STREAM)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;setsockopt(socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SOL_SOCKET, socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SO_REUSEADDR, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bind((TCP_IP, TCP_PORT))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tcp_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;listen(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Listening on port: &amp;#39;&lt;/span&gt;, TCP_PORT)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    input_sockets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [tcp_socket]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    output_sockets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []  &lt;span style=&#34;color:#75715e&#34;&gt;# Maybe use, we&amp;#39;ll see&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    xlist &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []  &lt;span style=&#34;color:#75715e&#34;&gt;# Not using&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# NEW: keeping track of which sockets we&amp;#39;re talking to over WS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ws_sockets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Get the sockets that are ready to read (the first of the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# three-tuple).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        readable_sockets &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; select&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;select(input_sockets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         output_sockets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         xlist,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; ready_socket &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; readable_sockets:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Make sure it&amp;#39;s not already closed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (ready_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fileno() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ready_socket &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; tcp_socket:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Handling main door socket&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                handle_new_connection(tcp_socket, input_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;### NEW CODE HERE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;elif&lt;/span&gt; ready_socket &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; ws_sockets:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;this is where we would handle the websocket message&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                handle_websocket_message(ready_socket, input_sockets,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                         ws_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;### END NEW CODE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Handling regular socket read&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                handle_request(ready_socket, input_sockets, ws_sockets)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle_websocket_message&lt;/span&gt;(client_socket, input_sockets, ws_sockets):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Handling WS message from client socket:&amp;#39;&lt;/span&gt;, client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;fileno())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# TODO: handle websocket message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see the full commit for this step &lt;a href=&#34;https://github.com/AlexanderEllis/websocket-from-scratch/commit/b5b6620e89a6db695921d14935fb1dff85bd8571&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Trying this out, we can see the successful handshake in action:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;~: $ curl -v 127.0.0.1:5006/websocket -H &amp;#34;Upgrade: websocket&amp;#34; -H &amp;#34;connection: Upgrade&amp;#34; -H &amp;#34;sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ==&amp;#34;
*   Trying 127.0.0.1:5006...
* Connected to 127.0.0.1 (127.0.0.1) port 5006 (#0)
&amp;gt; GET /websocket HTTP/1.1
&amp;gt; Host: 127.0.0.1:5006
&amp;gt; User-Agent: curl/7.77.0
&amp;gt; Accept: */*
&amp;gt; Upgrade: websocket
&amp;gt; connection: Upgrade
&amp;gt; sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ==
&amp;gt;
* Mark bundle as not supporting multiuse
&amp;lt; HTTP/1.1 101 Switching Protocols
&amp;lt; Upgrade: websocket
&amp;lt; Connection: Upgrade
&amp;lt; Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
&amp;lt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And on the server side:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;websockets-from-scratch: $ python3 foo.py
Listening on port:  5006
Handling main door socket
New socket 4 from address: (&amp;#39;127.0.0.1&amp;#39;, 54357)
Handling regular socket read
Handling request from client socket: 4
Received message:
GET /websocket HTTP/1.1
Host: 127.0.0.1:5006
User-Agent: curl/7.77.0
Accept: */*
Upgrade: websocket
connection: Upgrade
sec-websocket-key: dGhlIHNhbXBsZSBub25jZQ==


method, target, http_version: GET /websocket HTTP/1.1
headers:
{&amp;#39;host&amp;#39;: &amp;#39;127.0.0.1:5006&amp;#39;, &amp;#39;user-agent&amp;#39;: &amp;#39;curl/7.77.0&amp;#39;, &amp;#39;accept&amp;#39;: &amp;#39;*/*&amp;#39;, &amp;#39;upgrade&amp;#39;: &amp;#39;websocket&amp;#39;, &amp;#39;connection&amp;#39;: &amp;#39;Upgrade&amp;#39;, &amp;#39;sec-websocket-key&amp;#39;: &amp;#39;dGhlIHNhbXBsZSBub25jZQ==&amp;#39;}
request to ws endpoint!

response:
 HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What if we try with JS?&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const exampleSocket = new WebSocket(&amp;#34;ws://localhost:5006/websocket&amp;#34;);
exampleSocket.send(&amp;#39;foo&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Listening on port:  5006
Handling main door socket
New socket 4 from address: (&amp;#39;127.0.0.1&amp;#39;, 55842)
Handling regular socket read
Handling request from client socket: 4
Received message:
GET /websocket HTTP/1.1
Host: localhost:5006
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Upgrade: websocket
Origin: http://localhost:5006
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Sec-WebSocket-Key: mTNcafNQdQ6VrDiH5/ytoA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits


method, target, http_version: GET /websocket HTTP/1.1
headers:
{&amp;#39;host&amp;#39;: &amp;#39;localhost:5006&amp;#39;, &amp;#39;connection&amp;#39;: &amp;#39;Upgrade&amp;#39;, &amp;#39;pragma&amp;#39;: &amp;#39;no-cache&amp;#39;, &amp;#39;cache-control&amp;#39;: &amp;#39;no-cache&amp;#39;, &amp;#39;user-agent&amp;#39;: &amp;#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36&amp;#39;, &amp;#39;upgrade&amp;#39;: &amp;#39;websocket&amp;#39;, &amp;#39;origin&amp;#39;: &amp;#39;http://localhost:5006&amp;#39;, &amp;#39;sec-websocket-version&amp;#39;: &amp;#39;13&amp;#39;, &amp;#39;accept-encoding&amp;#39;: &amp;#39;gzip, deflate, br&amp;#39;, &amp;#39;accept-language&amp;#39;: &amp;#39;en-US,en;q=0.9&amp;#39;, &amp;#39;sec-websocket-key&amp;#39;: &amp;#39;mTNcafNQdQ6VrDiH5/ytoA==&amp;#39;, &amp;#39;sec-websocket-extensions&amp;#39;: &amp;#39;permessage-deflate; client_max_window_bits&amp;#39;}
request to ws endpoint!

response:
 HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: RfGS7Se2ChrHy1OSvUyWZmsOiJU=


this is where we would handle the websocket message
Handling WS message from client socket: 4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice! It looks like we&amp;rsquo;ve gotten all the way to having the data read into the
&lt;code&gt;handle_websocket_message&lt;/code&gt; call. Now comes the hard part.&lt;/p&gt;
&lt;h2 id=&#34;the-websocket-frames&#34;&gt;The WebSocket frames&lt;/h2&gt;
&lt;p&gt;Now that we can actually get the web sockets connected and send data on it, we
need to take the incoming bytes and translate them into a WebSocket frame.&lt;/p&gt;
&lt;p&gt;The WebSocket frame looks like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This looks complicated, but the main idea is that there are four sections:
flags, payload length, the masking key (maybe), and the payload data. You can
read about everything in &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc6455#section-5.2&#34;&gt;section 5.2&lt;/a&gt;,
but I&amp;rsquo;m going to cover a few particularly interesting ones here.&lt;/p&gt;
&lt;h3 id=&#34;masking&#34;&gt;Masking&lt;/h3&gt;
&lt;p&gt;First, a quick note about masking. Traffic can be (and sometimes must be)
&lt;em&gt;masked&lt;/em&gt; with a masking key, to prevent the WS traffic from mimicking other
traffic and HTTP requests, which has the potential to cause problems in
intermediary devices.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say your request goes through a cache that doesn&amp;rsquo;t know about
WebSocket connections and it caches responses based on their Host header. If
you&amp;rsquo;re communicating via WebSockets, you could make the payload
&lt;em&gt;look&lt;/em&gt; like a regular HTTP response, and the cache may try to cache it. If you
were nefarious, this would let you poison the cache and potentially redirect
requests at will.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;WebSocket gets around this by forcing the data to be masked with a random
number, making it hard to deterministically shape the traffic on the wire,  with
an algorithm that works in either direction:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Octet i of the transformed data (&amp;ldquo;transformed-octet-i&amp;rdquo;) is the XOR of&lt;/p&gt;
&lt;p&gt;octet i of the original data (&amp;ldquo;original-octet-i&amp;rdquo;) with octet at index&lt;/p&gt;
&lt;p&gt;i modulo 4 of the masking key (&amp;ldquo;masking-key-octet-j&amp;rdquo;):&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or, in (hopefully) more clear terms, for each byte of the payload, we bitwise OR
it with the corresponding byte of the random number, wrapping around with modulo.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;masking_key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# A 4-byte random number from the message&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;decoded_payload &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; index, byte &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; enumerate(encoded_payload):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  bitmask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; masking_key[index &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  decoded_byte &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; byte &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt; bitmask  &lt;span style=&#34;color:#75715e&#34;&gt;# Bitwise OR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  decoded_payload&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(decoded_byte)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This works in the other direction, too, since we&amp;rsquo;re really just doing bitwise
ORs here.&lt;/p&gt;
&lt;h3 id=&#34;back-to-the-frame&#34;&gt;Back to the frame&lt;/h3&gt;
&lt;p&gt;But, the payload isn&amp;rsquo;t always masked, though for client-&amp;gt;server it must be. The
&lt;code&gt;MASK&lt;/code&gt; flag, the first bit of the second byte, tells us whether or not it&amp;rsquo;s
present.&lt;/p&gt;
&lt;p&gt;This is the first hint that the layout of the frame can change based on its
contents. An &lt;a href=&#34;https://en.wikipedia.org/wiki/IPv4#Header&#34;&gt;IPv4 header can vary depending on the options&lt;/a&gt;,
but normally we&amp;rsquo;d expect the fields to be where the fields are. Interestingly
enough, that&amp;rsquo;s not the case for websockets.&lt;/p&gt;
&lt;p&gt;Along with the mask flag, there&amp;rsquo;s also the variation due to the varying number
of bytes we use to determine the payload length. This is a weird one, but it
depends on the initial 7 bits that are always in the same spot. The logic is as
follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;def get_payload_length():
  initial_payload_len = last 7 bits of the 2nd byte
  if initial_payload_len &amp;lt; 126:
    return initial_payload_len
  if initial_payload_len == 126:
    return those 7 bits with the next 2 bytes
  if initial_payload_len == 127:
    return those 7 bits with the next _8_ bytes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So where do we find our payload data? It depends! It depends on both having a
mask and the initial payload length from those 7 bits. There are only 6
different cases and we can write some code for it (and I did, which you can read
later), but it feels odd to look at. What do you mean the fields aren&amp;rsquo;t always
in the same place — isn&amp;rsquo;t that what the spec is for? How many bytes are we
really saving here?&lt;/p&gt;
&lt;h3 id=&#34;parsing-the-frame&#34;&gt;Parsing the frame&lt;/h3&gt;
&lt;p&gt;We can start thinking about how to translate these incoming bytes into some
representation of a WebSocket frame. Let&amp;rsquo;s say that we have some class
&lt;code&gt;WebsocketFrame&lt;/code&gt;, and we can populate it from a message made up of bytes. Let&amp;rsquo;s
assume for now that our bytes represent exactly one full frame. We can start
with the flags:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebsocketFrame&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_parse_flags&lt;/span&gt;(self, data_in_bytes):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      first_byte &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_in_bytes[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# Bad python formatting, but it helps to see where each one is.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_fin    &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; first_byte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0b10000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_rsv1   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; first_byte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0b01000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_rsv2   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; first_byte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00100000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_rsv3   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; first_byte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00010000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_opcode &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; first_byte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00001111&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      second_byte &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_in_bytes[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; second_byte &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0b10000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s not so bad — by visually lining it up, we can see where each one is.&lt;/p&gt;
&lt;p&gt;Next up would be parsing the payload length — this one is odd, because the
actual bytes we&amp;rsquo;ll translate into a number depend on the initial 7 bits that we
read, but if we need to combine those bits with later bytes, we need to only use
the 7 (and not the masking bit). Speaking of masking, because the payload length
also determines where we&amp;rsquo;ll find our mask key (as the position will vary), we
can keep track of that at the same time.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebsocketFrame&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_parse_payload_length&lt;/span&gt;(self, data_in_bytes):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# The payload length is the first 7 bits of the 2nd byte, or more if&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# it&amp;#39;s longer.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        payload_length &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (data_in_bytes[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0b01111111&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# We can also parse the mask key at the same time. If the payload&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# length is &amp;lt;126, the mask will start at the 3th byte.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mask_key_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Depending on the payload length, the masking key may be offset by&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# some number of bytes. We can assume big endian for now because I&amp;#39;m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# just running it locally.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; payload_length &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;126&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# If the length is 126, then the length also includes the next 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# bytes. Also parse length into first length byte to get rid of&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# mask bit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            payload_length &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_bytes(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                (bytes(payload_length) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; data_in_bytes[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                byteorder&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;big&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# This will also mean the mask is offset by 2 additional bytes.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            mask_key_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;elif&lt;/span&gt; payload_length &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;127&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# If the length is 127, then the length also includes the next 8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# bytes. Also parse length into first length byte to get rid of&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# mask bit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            payload_length &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;from_bytes(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                (bytes(payload_length) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; data_in_bytes[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;:&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                byteorder&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;big&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# This will also mean the mask is offset by 8 additional bytes.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            mask_key_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_payload_length &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; payload_length
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask_key_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; mask_key_start
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that we have our flags, our payload length, and where the mask starts, we
can parse the mask (if it exists). It&amp;rsquo;ll be the next 4 bytes from that position:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebsocketFrame&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_maybe_parse_masking_key&lt;/span&gt;(self, data_in_bytes):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_masking_key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_in_bytes[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask_key_start:self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask_key_start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, we have our flags, we have our payload length, we know where it starts,
we know if we&amp;rsquo;re masked, and we have the mask. We can finally go through the
payload data, decode like before, and generate the final data (and add a getter
for it):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebsocketFrame&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_parse_payload&lt;/span&gt;(self, data_in_bytes):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# All client data_in_bytes should be masked&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        payload_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;b&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_payload_length &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; payload_data
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Get the masking key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            payload_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask_key_start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            encoded_payload &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_in_bytes[payload_start:]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# To decode the payload, we do a bitwise OR with the mask at the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# mask index determined by the payload index modulo 4.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            decoded_payload &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                byte &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_masking_key[i &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i, byte &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; enumerate(encoded_payload)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            payload_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; bytes(decoded_payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# If we don&amp;#39;t have a mask, the payload starts where the mask would&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# have.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            payload_start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_mask_key_start
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            payload_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data_in_bytes[payload_start:]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_payload_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; payload_data
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_payload_data&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;_payload_data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we put that all together, we can update our &lt;code&gt;handle_websocket_message&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handle_websocket_message&lt;/span&gt;(client_socket, input_sockets, ws_sockets):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Let&amp;#39;s assume that we get a full single frame in each recv (may not&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# be true IRL)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data_in_bytes &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;recv(BUFFER_SIZE)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_frame &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; toy_websocket_frame&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;WebsocketFrame()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    websocket_frame&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;populateFromWebsocketFrameMessage(data_in_bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Received message:&amp;#39;&lt;/span&gt;, websocket_frame&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_payload_data()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And finally, we can run it in the browser console:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;final-js-testing.png&#34; alt=&#34;Screenshot showing me creating the WebSocket in JavaScript and sending &amp;amp;lsquo;foo&amp;amp;rsquo;&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;final-terminal-testing.png&#34; alt=&#34;Screenshot the terminal printing the message it received, &amp;amp;lsquo;foo&amp;amp;rsquo;&#34;&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll notice I also included a much longer message, with over 127 bytes, just
to make sure we were parsing correctly. It took a little debugging, but it
works!&lt;/p&gt;
&lt;p&gt;The code is rough because it&amp;rsquo;s just for fun, but you can see the full thing
&lt;a href=&#34;https://github.com/AlexanderEllis/websocket-from-scratch&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;fun-with-toy-programs&#34;&gt;Fun with toy programs&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a short and incomplete list of the things I haven&amp;rsquo;t touched on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sending data back&lt;/li&gt;
&lt;li&gt;Message fragmentation: what if I don&amp;rsquo;t have a full message? I would need to
keep track of the payload length received so far.&lt;/li&gt;
&lt;li&gt;Threading: what would be a good threading model for these messages? How much
would you have to keep in shared buffers as you wait for the rest of
incomplete messages?&lt;/li&gt;
&lt;li&gt;How should we handle opcodes? Closing the connection? Origin checks?&lt;/li&gt;
&lt;li&gt;Extensions? Subprotocols?&lt;/li&gt;
&lt;li&gt;How should we actually encode our data?&lt;/li&gt;
&lt;li&gt;WSS and encrypting our WS connections?&lt;/li&gt;
&lt;li&gt;And probably many, many more&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;I&amp;rsquo;m so grateful we&amp;rsquo;ve had smart people working on these things already. Much
like &lt;a href=&#34;https://alexanderell.is/posts/rpc-from-scratch&#34;&gt;my post on RPCs&lt;/a&gt;, writing this from
scratch made me really appreciate the libraries we already have to do this work
for us. But, even with a tiny slice of reality, this gave us some insight into
the handshake, the details of the handshake, the frame, how these things are
built, and the problems they have to solve.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://beej.us/guide/bgnet/&#34;&gt;Beej&amp;rsquo;s Guide to Network Programming&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;See something like &lt;a href=&#34;https://stackoverflow.com/questions/14174184/what-is-the-mask-in-a-websocket-frame#:~:text=Masking%20of%20WebSocket%20traffic%20from,an%20attack%20of%20some%20kind.&#34;&gt;this&lt;/a&gt; for an even better explanation.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>What happens if you point two CNAMEs at each other? Not much, really</title>
      <link>https://alexanderell.is/posts/cnames/</link>
      <pubDate>Sun, 14 Aug 2022 20:46:39 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/cnames/</guid>
      <description>&lt;p&gt;I dabble with DNS for work, and I&amp;rsquo;m frequently checking if CNAMEs are properly
configured. CNAMEs are Canonical NAMEs, kind of like nicknames, that indicate
that one domain name is a nickname for another domain name. For example, you
probably encounter CNAMEs that map from the classic &lt;code&gt;www&lt;/code&gt; subdomain:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;www.example.com CNAME example.com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This entry indicates that &lt;code&gt;example.com&lt;/code&gt; is the Canonical NAME for
&lt;code&gt;www.example.com&lt;/code&gt;. When you go to &lt;code&gt;www.example.com&lt;/code&gt;, your DNS resolver sees this
entry and looks up the right side instead, &lt;code&gt;example.com&lt;/code&gt;, which then (hopefully)
resolves to the actual destination. This is pretty neat, and it can be handy for
doing all sorts of interesting things with subdomains.&lt;/p&gt;
&lt;p&gt;If you aren&amp;rsquo;t familiar with CNAMEs or DNS, I&amp;rsquo;d highly, highly recommend
&lt;a href=&#34;https://jvns.ca/blog/2022/04/26/new-zine--how-dns-works-/&#34;&gt;Julia Evans&amp;rsquo; &lt;em&gt;How DNS Works!&lt;/em&gt; Zine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was thinking about CNAMEs the other day and a silly question popped into my
head: if CNAMEs are used to define domain names that are the canonical names for
other domain names, what if we defined another CNAME that just pointed back to
the first? What if there was a cycle?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;you-fool.png&#34; alt=&#34;Screenshot of my DNS config with a.alexanderell.is pointing to b.alexanderell.is and vice versa&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&#34;tldr-nothing-terribly-exciting-happens-they-thought-about-this-back-in-1993&#34;&gt;TL;DR nothing terribly exciting happens. They thought about this back in 1993.&lt;/h3&gt;
&lt;p&gt;We can see what this looks like with a quick &lt;code&gt;dig&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;blog: $ dig a.alexanderell.is

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.10.6 &amp;lt;&amp;lt;&amp;gt;&amp;gt; a.alexanderell.is
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: SERVFAIL, id: 29976
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; Query time: 172 msec
;; SERVER: 2001:558:feed::1#53(2001:558:feed::1)
;; WHEN: Sun Aug 14 21:36:47 EDT 2022
;; MSG SIZE  rcvd: 46
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well that&amp;rsquo;s not very exciting. There was a &lt;code&gt;SERVFAIL&lt;/code&gt; on the query.&lt;/p&gt;
&lt;p&gt;What does the full query look like starting from the root servers?&lt;/p&gt;
&lt;p&gt;(Feel free to skip to the &lt;a href=&#34;#break-down&#34;&gt;end of the output&lt;/a&gt;, where I walk through it query by
query).&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;blog: $ dig +trace +all a.alexanderell.is

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.10.6 &amp;lt;&amp;lt;&amp;gt;&amp;gt; +trace +all a.alexanderell.is
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 45480
;; flags: qr ra ad; QUERY: 1, ANSWER: 14, AUTHORITY: 0, ADDITIONAL: 27

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;.				IN	NS

;; ANSWER SECTION:
.			483007	IN	NS	c.root-servers.net.
.			483007	IN	NS	d.root-servers.net.
.			483007	IN	NS	e.root-servers.net.
.			483007	IN	NS	f.root-servers.net.
.			483007	IN	NS	g.root-servers.net.
.			483007	IN	NS	h.root-servers.net.
.			483007	IN	NS	i.root-servers.net.
.			483007	IN	NS	j.root-servers.net.
.			483007	IN	NS	k.root-servers.net.
.			483007	IN	NS	l.root-servers.net.
.			483007	IN	NS	m.root-servers.net.
.			483007	IN	NS	a.root-servers.net.
.			483007	IN	NS	b.root-servers.net.
.			483007	IN	RRSIG	NS 8 0 518400 20220827050000 20220814040000 20826 . oQpyr4pIhGLgcV1GUSYSasiMsvbkTkZ6ZkXyjDQsA5nMob/9YnXX7gAX aTODvODzX68dG6au5/lFHkuH+gbbatHMNQfLND9B1Fah2UVVRKkd10N/ UVCoh4QE3g8ktWvdBriKUfgPC/0iizk1mx/vS0I8M1n9ey9zN8r+vkfd i9izcHE7sX6OCe5OaTTILMDbBZPSp6fJ9Idci3iToPJ3Y23esRanMEqp 0aY2UNpf/oFYW8u/PduugJmyfvp4WZ1lqvWCxx43oSVI/b9QVG9o08Hr 6dQOFmYa7mui65nP7kFW/lWvwP43lNHdpFOu7L9KCzZOenEAT3lTXMUA b7+4fQ==

;; ADDITIONAL SECTION:
a.root-servers.net.	313522	IN	A	198.41.0.4
a.root-servers.net.	313582	IN	AAAA	2001:503:ba3e::2:30
b.root-servers.net.	313628	IN	A	199.9.14.201
b.root-servers.net.	313558	IN	AAAA	2001:500:200::b
c.root-servers.net.	313615	IN	A	192.33.4.12
c.root-servers.net.	313577	IN	AAAA	2001:500:2::c
d.root-servers.net.	313623	IN	A	199.7.91.13
d.root-servers.net.	313621	IN	AAAA	2001:500:2d::d
e.root-servers.net.	313720	IN	A	192.203.230.10
e.root-servers.net.	313578	IN	AAAA	2001:500:a8::e
f.root-servers.net.	313629	IN	A	192.5.5.241
f.root-servers.net.	313544	IN	AAAA	2001:500:2f::f
g.root-servers.net.	314027	IN	A	192.112.36.4
g.root-servers.net.	313600	IN	AAAA	2001:500:12::d0d
h.root-servers.net.	313616	IN	A	198.97.190.53
h.root-servers.net.	313577	IN	AAAA	2001:500:1::53
i.root-servers.net.	313614	IN	A	192.36.148.17
i.root-servers.net.	313614	IN	AAAA	2001:7fe::53
j.root-servers.net.	313614	IN	A	192.58.128.30
j.root-servers.net.	313561	IN	AAAA	2001:503:c27::2:30
k.root-servers.net.	313615	IN	A	193.0.14.129
k.root-servers.net.	313561	IN	AAAA	2001:7fd::1
l.root-servers.net.	313617	IN	A	199.7.83.42
l.root-servers.net.	313617	IN	AAAA	2001:500:9f::42
m.root-servers.net.	313517	IN	A	202.12.27.33
m.root-servers.net.	313515	IN	AAAA	2001:dc3::35

;; Query time: 22 msec
;; SERVER: 2001:558:feed::1#53(2001:558:feed::1)
;; WHEN: Sun Aug 14 21:41:50 EDT 2022
;; MSG SIZE  rcvd: 1097

;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 4649
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 8, ADDITIONAL: 13

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; AUTHORITY SECTION:
is.			172800	IN	NS	sunic.sunet.se.
is.			172800	IN	NS	durinn.rhnet.is.
is.			172800	IN	NS	bes.isnic.is.
is.			172800	IN	NS	tg-tldsecondary01.isnic.is.
is.			172800	IN	NS	ht-tldsecondary01.isnic.is.
is.			172800	IN	NS	sab.isnic.is.
is.			86400	IN	DS	179 8 2 D45BB61D3BC69EF89B877439444854151B3EC3A2A9E3E740A3D89DBB 5B5CD15B
is.			86400	IN	RRSIG	DS 8 1 86400 20220827170000 20220814160000 20826 . KYXFNCkA+RqpWwEnNH/dpCVdTCQ5b51nU9bFighuq7OkG9LKQS4esY6z hpFfpTkeZSOPXeVQV6Ijyw/T4egEFFkpD3yst5kroHcHNwsoQJ9VTX8R 5b9Y/VVO1KSDWRHz1ImReHA+FyndqWOv+vCvfSIDc4ntdMdq7xpbjsjH HqsgslYdn+5yyb1Y/O4ioMyPfC0W2bCmmUwiwpazZBXmwAFHyYhtVMSb vCEX4DwshK6q3QrN86o/PC33xdMJB84bXSSvd7vqRylNOcMVoy9T6l6u eBWi50XccRv58GUB/WHO3CwF9quMtw/qTe9hhF0hax0HpDUJzDxRW/dD k42EvA==

;; ADDITIONAL SECTION:
tg-tldsecondary01.isnic.is. 172800 IN	A	185.93.156.154
ht-tldsecondary01.isnic.is. 172800 IN	A	185.93.156.10
durinn.rhnet.is.	172800	IN	A	130.208.16.20
sunic.sunet.se.		172800	IN	A	192.36.125.2
sab.isnic.is.		172800	IN	A	194.146.106.58
bes.isnic.is.		172800	IN	A	204.61.216.116
tg-tldsecondary01.isnic.is. 172800 IN	AAAA	2001:67c:6c:f056::154
ht-tldsecondary01.isnic.is. 172800 IN	AAAA	2001:67c:6c:56::10
durinn.rhnet.is.	172800	IN	AAAA	2a00:c88:10:16::20
sunic.sunet.se.		172800	IN	AAAA	2001:6b0:7::2
sab.isnic.is.		172800	IN	AAAA	2001:67c:1010:14::53
bes.isnic.is.		172800	IN	AAAA	2001:500:14:6116:ad::1

;; Query time: 90 msec
;; SERVER: 202.12.27.33#53(202.12.27.33)
;; WHEN: Sun Aug 14 21:41:50 EDT 2022
;; MSG SIZE  rcvd: 812

;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 4229
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 5, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; AUTHORITY SECTION:
alexanderell.is.	86400	IN	NS	ns3.digitalocean.com.
alexanderell.is.	86400	IN	NS	ns1.digitalocean.com.
alexanderell.is.	86400	IN	NS	ns2.digitalocean.com.
ffmia098sr21vlstesk9tcas3o8r2kbi.is. 1800 IN NSEC3 1 0 0 - FFN0KB1I2OUM49H3U27J2Q5TIJ2ID6U9  NS
ffmia098sr21vlstesk9tcas3o8r2kbi.is. 1800 IN RRSIG NSEC3 8 2 1800 20220823181002 20220809164002 27694 is. jAg9RbYPjOHbxBPjJaBe9v2/y1A8h8mBHXzvliihyXw/ZPJa7nFA2q/O rdmYNgHMbF25qePTv0lQXsohVHXcvTxCY6lVND0YDu3FfsQ2zdfBSpzz wOq2mUogLduIAavbx6EmNow4nvbGR4Lg3aiFVjVI+l9ra8WVrPWrOf62 wx6JfezdbXNimgRMRVn8ndSHvJPSQfyRpAjjxiAa4WQqfqRuPujSNOjD IwGk6k2hfTambCbDHXY5QKOPoE0fVAXtCKS4YbcJlIgI6m/POE1Pz92Y kK223LnTTPQULyeuzd57V1imk6gKDtH4SiW2KhuxsuUhzq3ZryWSHHwu 0DtE4Q==

;; Query time: 117 msec
;; SERVER: 2001:67c:6c:56::10#53(2001:67c:6c:56::10)
;; WHEN: Sun Aug 14 21:41:51 EDT 2022
;; MSG SIZE  rcvd: 482

;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: SERVFAIL, id: 28117
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; Query time: 97 msec
;; SERVER: 198.41.222.173#53(198.41.222.173)
;; WHEN: Sun Aug 14 21:41:51 EDT 2022
;; MSG SIZE  rcvd: 46
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a id=&#34;break-down&#34;&gt;&lt;/a&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;dig&lt;/code&gt; output can look gnarly, but let&amp;rsquo;s break this down. &lt;code&gt;dig&lt;/code&gt; is
recursively asking &lt;em&gt;&amp;ldquo;Where do I find this domain?&amp;rdquo;&lt;/em&gt;, starting with the root
servers and asking the DNS server in the answer. You can imagine it walking down
a tree of DNS servers as they get more and more specific for the domain in
question.&lt;/p&gt;
&lt;p&gt;You can see the first request in the first &lt;code&gt;QUESTION SECTION&lt;/code&gt;: &lt;code&gt;. IN NS&lt;/code&gt;, or
&lt;em&gt;&amp;ldquo;I&amp;rsquo;m looking for any domain. Who are the root servers?&amp;rdquo;&lt;/em&gt;. This query did fine,
and it returned the root servers and where to find them.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 45480
;; flags: qr ra ad; QUERY: 1, ANSWER: 14, AUTHORITY: 0, ADDITIONAL: 27

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;.				IN	NS

;; ANSWER SECTION:
.			483007	IN	NS	m.root-servers.net.
... More root servers

;; ADDITIONAL SECTION:
m.root-servers.net.	313522	IN	A	202.12.27.33
... where to find other root servers
&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;p&gt;The next question was to ask the root servers about this domain. We can see it
asked the server with the IP &lt;code&gt;202.12.27.33&lt;/code&gt;, which corresponds to
&lt;code&gt;m.root-servers.net&lt;/code&gt; above. We asked it how to find &lt;code&gt;a.alexanderell.is&lt;/code&gt;, and it
pointed us towards the &lt;code&gt;.is&lt;/code&gt; TLD name servers (and a Swedish name server, see
the &lt;code&gt;.se&lt;/code&gt;). Again we can see that there was no error, which makes sense.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 4649
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 8, ADDITIONAL: 13

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; AUTHORITY SECTION:
is.			172800	IN	NS	sunic.sunet.se.
is.			172800	IN	NS	durinn.rhnet.is.
... More Icelandic servers

;; ADDITIONAL SECTION:
durinn.rhnet.is.	172800	IN	A	130.208.16.20
sunic.sunet.se.		172800	IN	A	192.36.125.2

;; Query time: 90 msec
;; SERVER: 202.12.27.33#53(202.12.27.33)
&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;p&gt;Next up, we can see the query to one of the &lt;code&gt;.is&lt;/code&gt; TLD name servers,
&lt;code&gt;2001:67c:6c:56::10&lt;/code&gt;, which corresponds to &lt;code&gt;ht-tldsecondary01.isnic.is&lt;/code&gt;. It
responded with the DigitalOcean name servers, because I originally set up my
Icelandic hostname with them to point at a droplet, though I now just use
DigitalOcean for DNS things, though I probably don&amp;rsquo;t need them any more.
Regardless, again we see no error.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 4229
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 5, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; AUTHORITY SECTION:
alexanderell.is.	86400	IN	NS	ns3.digitalocean.com.
... Other DigitalOcean name servers

;; Query time: 117 msec
;; SERVER: 2001:67c:6c:56::10#53(2001:67c:6c:56::10)
&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;p&gt;Finally, we can see the query to the DigitalOcean name server for this silly
setup. We got nothing back, other than a final result that there was a
&lt;code&gt;SERVFAIL&lt;/code&gt;, which, as you may have guessed, means there was a failure on the
server.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: SERVFAIL, id: 28117
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; Query time: 97 msec
;; SERVER: 198.41.222.173#53(198.41.222.173)
;; WHEN: Sun Aug 14 21:41:51 EDT 2022
;; MSG SIZE  rcvd: 46
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I think it&amp;rsquo;s pretty likely that the DigitalOcean name server started to look
this up and quickly realized they were dealing with me, a fool, who pointed a
CNAME to a CNAME that pointed back to the first CNAME. This seems like it could
be a pretty basic cycle check, and it makes sense that they&amp;rsquo;re guarding against
such cases.&lt;/p&gt;
&lt;h3 id=&#34;what-if-we-went-outside-of-digitalocean&#34;&gt;What if we went outside of DigitalOcean?&lt;/h3&gt;
&lt;p&gt;Maybe the issue is that we&amp;rsquo;re giving the DigitalOcean name server too much
freedom to squash our silly attempts. What if we split the CNAMEs across name
servers? I just happened to have a Google domain for a side project I&amp;rsquo;ll get
around to someday, and it&amp;rsquo;s pretty easy to point them at each other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Google Domains DNS setup:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;google-dns.png&#34; alt=&#34;Screenshot of my Google DNS config with a.reviewscheduler.com pointing to a.alexanderell.is&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DigitalOcean DNS setup:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;do-dns.png&#34; alt=&#34;Screenshot of my DigitalOcean DNS config with a.alexanderell.is pointing to a.reviewscheduler.com&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is some seriously silly stuff right here. Giving this a shot, we can see
that spreading these CNAMEs across two name servers made a difference, and we
can now see both CNAME entries from a single dig:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;blog: $ dig a.alexanderell.is

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.10.6 &amp;lt;&amp;lt;&amp;gt;&amp;gt; a.alexanderell.is
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 58919
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;a.alexanderell.is.		IN	A

;; ANSWER SECTION:
a.alexanderell.is.	42393	IN	CNAME	a.reviewscheduler.com.
a.reviewscheduler.com.	3600	IN	CNAME	a.alexanderell.is.

;; Query time: 358 msec
;; SERVER: 2001:558:feed::1#53(2001:558:feed::1)
;; WHEN: Sun Aug 14 22:30:49 EDT 2022
;; MSG SIZE  rcvd: 95
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you look at the traces (exercise left to the reader), you can see that
there&amp;rsquo;s a little more going on under the hood, in particular what looks like
proactive lookup ahead of time on Google&amp;rsquo;s side. Importantly though, these
servers find that the end result is another CNAME pointing back to the original,
and they go no further.&lt;/p&gt;
&lt;p&gt;If we now try to curl one of these hosts, the DNS resolution times out after a
while, as it&amp;rsquo;s now probably trying these CNAMEs back to back, looping as it
makes futile attempts to resolve. Sorry, curl.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;blog: $ curl -v a.alexanderell.is
... 30 seconds later
* Could not resolve host: a.alexanderell.is
* Closing connection 0
curl: (6) Could not resolve host: a.alexanderell.is
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;what-does-the-dns-spec-say&#34;&gt;What does the DNS spec say?&lt;/h3&gt;
&lt;p&gt;If we go back to 1993, &lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc1536&#34;&gt;RFC 1536&lt;/a&gt;
explored some of these issues:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;2. Recursion Bugs

   When a server receives a client request, it first looks up its zone
   data and the cache to check if the query can be answered. If the
   answer is unavailable in either place, the server seeks names of
   servers that are more likely to have the information, in its cache or
   zone data. It then does one of two things. If the client desires the
   server to recurse and the server architecture allows recursion, the
   server chains this request to these known servers closest to the
   queried name. If the client doesn&amp;#39;t seek recursion or if the server
   cannot handle recursion, it returns the list of name servers to the
   client assuming the client knows what to do with these records.

   The client queries this new list of name servers to get either the
   answer, or names of another set of name servers to query. This
   process repeats until the client is satisfied. Servers might also go
   through this chaining process if the server returns a CNAME record
   for the queried name. Some servers reprocess this name to try and get
   the desired record type.

   However, in certain cases, this chain of events may not be good. For
   example, a broken or malicious name server might list itself as one
   of the name servers to query again. The unsuspecting client resends
   the same query to the same server.

   In another situation, more difficult to detect, a set of servers
   might form a loop wherein A refers to B and B refers to A. This loop
   might involve more than two servers.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s us!&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;FIXES:

      a. Set an upper limit on the number of referral links and CNAME
         links you are willing to chase.

         Note that this is not guaranteed to break only recursion loops.
         It could, in a rare case, prune off a very long search path,
         prematurely.  We know, however, with high probability, that if
         the number of links cross a certain metric (two times the depth
         of the DNS tree), it is a recursion problem.

      b. Watch out for self-referring servers. Avoid them whenever
         possible.

      c. Make sure you never pass off an authority NS record with your
         own name on it!

      d. Fix clients to accept iterative answers from servers not built
         to provide recursion. Such clients should either be happy with
         the non-authoritative answer or be willing to chase the
         referral links themselves.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No surprises here — these all make sense as ways to defend. This does bring up
another interesting question though: what&amp;rsquo;s the longest valid DNS chain we can
make? Another exercise left to the reader.&lt;/p&gt;
&lt;h3 id=&#34;what-other-things-are-out-there&#34;&gt;What other things are out there?&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m no expert on DNS servers (I&amp;rsquo;m more of an &amp;ldquo;application layer well after DNS&amp;rdquo;
guy), but after a quick search, I found an interesting article about
&lt;a href=&#34;https://www.sidnlabs.nl/en/news-and-blogs/tsuname-dns-loops-are-a-well-known-problem-but-arent-properly-addressed-by-current-rfcs&#34;&gt;DNS loops and potential attack vectors&lt;/a&gt;,
where they found a real-world example where NS record cycles led to a
magnification of queries to the authoritative servers. In their words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Last May, we publicly disclosed tsuNAME, a DNS vulnerability that could be
exploited to mount DDoS attacks, where resolvers, clients and/or forwarders
send endless queries to authoritative DNS servers. Although earlier RFCs have
documented the existence of DNS name loops, none of them have fully addressed
the problem. To fix that, we have proposed a new IETF draft to the DNS
Operations Working Group (DNSOP WG).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From &lt;a href=&#34;https://tsuname.io/tech_report.pdf&#34;&gt;their technical report on tsuNAME&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;On 2020-02-01, two domains (DomainA and DomainB) under .nz had their NS
records misconfigured to be cyclically dependent. DomainANS records were
pointed to ns[1,2].DomainB.nz, while DomainB NS records pointed to
ns[1,2].DomainA.nz. This configuration error led to a 50% surge in query
volume at .nz authoritative servers (Figure 1) . The .nz operators manually
fixed this misconfiguration on 2020-02-17, after which the queries to return
to normal levels&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From the &lt;em&gt;Emulating TSUNAME&lt;/em&gt; section, they describe recreating the issue with
their own authoritative servers. They used probes to send ~18,000 DNS queries to
their first hop resolvers, and those requests were eventually &lt;em&gt;magnified to
~8,000,000 queries to the authoritative servers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Recommend reading through the rest of the technical report if you&amp;rsquo;re curious —
it&amp;rsquo;s pretty interesting stuff.&lt;/p&gt;
&lt;p&gt;Glad I stopped at CNAMEs.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sharpening the axe for programming</title>
      <link>https://alexanderell.is/posts/sharpening-the-axe/</link>
      <pubDate>Sat, 06 Aug 2022 15:46:51 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/sharpening-the-axe/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A woodsman was once asked, “What would you do if you had just five minutes to
chop down a tree?” He answered, “I would spend the first two and a half
minutes sharpening my axe.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When it comes to solving a problem with code, I&amp;rsquo;ve found a few things that help
make the process more efficient. The code is the output, but like
sharpening the axe, there are a few things I do outside of writing code that
makes it easier. I&amp;rsquo;ll be walking through a few of them below.&lt;/p&gt;
&lt;p&gt;Quick note that this isn&amp;rsquo;t a guide to sharpening your own programming axe; that
you&amp;rsquo;ll have to figure out on your own! While I encourage everyone to think
deliberately about how they work, this is just a few things I&amp;rsquo;ve found work for
my own brain.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;explaining-myself&#34;&gt;Explaining myself&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve found that one of the best ways to think through a tricky problem is to
explain it first. Sometimes it&amp;rsquo;s a formal design document for other people to
spread awareness and build consensus. Sometimes, it&amp;rsquo;s a rubber-duck debugging
session as I explain the problem to my dog. Sometimes, it&amp;rsquo;s even a scribbled
diagram on a throwaway piece of paper that helps me visualize the solution.&lt;/p&gt;
&lt;p&gt;Putting the problem into words forces you to think through it and requires that
you be exact about it. There&amp;rsquo;s nothing quite like attempting to go through a
detailed explanation to force you to really understand what&amp;rsquo;s going on (extra
points if you&amp;rsquo;re explaining it to someone else), and you quickly uncover areas
you don&amp;rsquo;t fully understand. Those areas you don&amp;rsquo;t understand are the ones that
can make coming up with a solution and implementing it difficult, and working
through them ahead of time can save you from so many later troubles.&lt;/p&gt;
&lt;p&gt;Additional extra points if you have someone already familiar with the problem
take a look at what you&amp;rsquo;ve written to confirm your understanding. It&amp;rsquo;s a great
way to learn, and it&amp;rsquo;s a strong benefit to going through the writing process.&lt;/p&gt;
&lt;h3 id=&#34;deliberately-exploring-and-chipping-away&#34;&gt;Deliberately exploring and chipping away&lt;/h3&gt;
&lt;p&gt;When working through a tricky problem, it can be so tempting to noodle, and I&amp;rsquo;d
be lying if I said I had never rerun a test after randomly changing a line or
two without fully thinking through it. I try not to, though. I&amp;rsquo;m a huge fan of
&lt;a href=&#34;https://alexanderell.is/posts/debugging-lab-notebook/&#34;&gt;keeping a debugging lab notebook&lt;/a&gt;, and I often
find myself writing summaries and updates of what I&amp;rsquo;ve tried and what I&amp;rsquo;m seeing
to help keep my exploration guided, even though no one will ever read them. Most
of the time, they&amp;rsquo;re a winding path of things I uncover and think about as I
work through a problem, including the next steps I&amp;rsquo;m planning to take.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a combination of writing it down (like earlier) and being deliberate that
helps me. Keeping track of what I&amp;rsquo;ve done, where I&amp;rsquo;ve been, and the immediate
next steps helps me deliberately build my understanding, as I write down
questions and dig in to the answers. It&amp;rsquo;s like a targeted exploration of the
problem, where you take individual steps to build your understanding, explore
the problem, and get closer to a solution.&lt;/p&gt;
&lt;p&gt;I really enjoy this Julia Evans comic about debugging: just focus on one
question at a time, and slowly build understanding from there!&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;debugging strategy: come up with one question &lt;a href=&#34;https://t.co/2Lytzl4laQ&#34;&gt;pic.twitter.com/2Lytzl4laQ&lt;/a&gt;&lt;/p&gt;&amp;mdash; 🔎Julia Evans🔍 (@b0rk) &lt;a href=&#34;https://twitter.com/b0rk/status/1554120424602193921?ref_src=twsrc%5Etfw&#34;&gt;August 1, 2022&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;br&gt;
&lt;p&gt;When faced with ambiguous problems and a difficult search space, I find it
helpful to take those individual steps. I write down a few questions, then go
through answering each one. They often result in more questions, but they also
result in progress.&lt;/p&gt;
&lt;p&gt;Much like writing notes throughout the day, I find that the best benefit is from
the process and not saving my steps for the future. Usually my debugging
notebooks waste away as bytes on a Google Drive server somewhere, but
occasionally I&amp;rsquo;ll see a similar problem and be able to go back and see what
steps I took before. Like visiting old code, it&amp;rsquo;s also sometimes fun to go back
with my current knowledge and see how I thought about a problem at the time.&lt;/p&gt;
&lt;h3 id=&#34;passing-it-off-to-a-background-thread&#34;&gt;Passing it off to a background thread&lt;/h3&gt;
&lt;p&gt;One of the best ways for me to make forward progress is to step away from the
computer and &lt;a href=&#34;https://alexanderell.is/posts/trust-in-your-unconscious/&#34;&gt;let my brain passively think about the problem&lt;/a&gt;.
It can be difficult to remember to do sometimes, especially when I&amp;rsquo;m stuck deep
in a problem whose solution feels like it&amp;rsquo;s right around the corner: what if I
just tried this one other thing?&lt;/p&gt;
&lt;p&gt;In the moment, it can feel counterintuitive, especially when you&amp;rsquo;re deep in the
weeds.  I&amp;rsquo;ve never regretted taking a break, and more often than not, something
in the background will fire and make a connection that makes sense and helps
uncover some mystery.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If none of this works, I’ve found that one of the best ways to move forward
with a problem is to get away from the problem. If it’s halfway through the
day, going for a walk around the block (importantly, without just going on my
phone) or going for a swim will often let me realize something I missed that
proves to be the key when I get back to my desk. If it’s close to the end of
the day, heading home and letting the problem sit overnight is often enough to
unblock me for the next morning. I’ve lost track of how many times I’ve
realized a key insight on the train home, only 15 minutes of unfocused day
dreaming since I was just looking intently at the code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Part of the game for me is making it easy for my brain to do the difficult work.
I once saw a question about whether or not typing speed was important to be a
proficient programmer, and I don&amp;rsquo;t think it is. For programming, typing the code
in is the easy part; it&amp;rsquo;s understanding systems and the existing code, deciding
which code to write, and figuring out &lt;em&gt;how&lt;/em&gt; to write it that are the hard parts.
Luckily, I&amp;rsquo;ve found a few things that make it all easier.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Funny artifacts in a 3D house scan</title>
      <link>https://alexanderell.is/posts/3d-scan/</link>
      <pubDate>Sat, 30 Jul 2022 13:16:31 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/3d-scan/</guid>
      <description>&lt;p&gt;I like to window shop houses on Zillow. I really enjoy the recent trend of
3D interior scans, as they help give you a feeling for what the place is like,
and it allows for another way to explore the space without seeing it in person,
which is key for a good cross-country window shopping experience.&lt;/p&gt;
&lt;p&gt;The other day, I was looking at a 4 story townhouse, and I noticed a few weird
things on its 3D model. These models are frequently weird to look at because of
the exterior wall artifacts, but this one was extra weird.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;3d-house-scan.png&#34; alt=&#34;A 3D scan of a house showing a bed floating outside the house&#34;&gt;&lt;/p&gt;
&lt;p&gt;Is that a bed floating in the void?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;floating-bed.png&#34; alt=&#34;A bed just floating outside the house in the void&#34;&gt;&lt;/p&gt;
&lt;p&gt;Why yes, I think it is! But the bed looks familiar: it&amp;rsquo;s a weird copy of the
bed in the room next to it.&lt;/p&gt;
&lt;p&gt;Is this the bed&amp;rsquo;s shadow self? The Upside Down?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;bed-duplicate.png&#34; alt=&#34;The floating bed is very similar to the bed in the room&#34;&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll give you a moment to think about why this may be happening. When you&amp;rsquo;re
ready, click the spoiler tag below.&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Spoiler: why there is a floating bed&lt;/summary&gt;
  &lt;br&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;floating-bed-screencast.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;floating-bed-screencast.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;
&lt;br&gt;
&lt;p&gt;It looks like the 3D camera was bouncing its sensing waves off the mirror!
It saw a bed through the mirror, and it couldn&amp;rsquo;t tell whether the thing it
saw was a real bed, maybe through a window, or a bed&amp;rsquo;s reflection in the mirror.
Additionally, because the vantage point of the camera was a limited section of
the bed through the mirror, it thought it was a triangle shaped bed — the right
side of the ghost bed is truncated along the camera&amp;rsquo;s line of sight at the edge
of the mirror.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the position the camera was in when it was doing the 3D scan of this room:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;mirror-view.png&#34; alt=&#34;The camera&amp;amp;rsquo;s point of view, showing the bed in the mirror&#34;&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s so cool that the camera can create a (mostly working) 3D model from just a
single vantage point, but you can see how important the additional scans are for
verifying and completing the models.&lt;/p&gt;
&lt;p&gt;There are a few more in here, too, like these windows that are translated&amp;hellip;
scarily.&lt;/p&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;scary-windows-screencast.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;scary-windows-screencast.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;/details&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href=&#34;https://www.zillow.com/homedetails/397-Bunker-Hill-St-Charlestown-MA-02129/59212360_zpid/?mmlb=p,0&#34;&gt;Here&amp;rsquo;s a link if you want to play around with the 3D model yourself.&lt;/a&gt; It&amp;rsquo;s fun to think about how these things work!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>My false start when learning to program</title>
      <link>https://alexanderell.is/posts/learning-programming-false-start/</link>
      <pubDate>Sat, 16 Jul 2022 16:35:10 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/learning-programming-false-start/</guid>
      <description>&lt;p&gt;I got into programming after college, but there was a brief period of time when
I had first tried to learn how to program. It didn&amp;rsquo;t stick, and I was thinking
the other day about why that first attempt didn&amp;rsquo;t keep.&lt;/p&gt;
&lt;p&gt;It was during one of my college summers (2012 or so) when I was working as an
intern at a small law firm in Vermont. My day to day duties varied heavily, with
some days being very busy and other days being filled with nothing to do, and in
some of that downtime, I decided to learn to program. I had met a few people at
school who were studying computer science, and although I knew next to nothing
about what it actually entailed, I thought it may be something that would be
good to do.&lt;/p&gt;
&lt;p&gt;I had heard about the introductory JavaScript classes on Codecademy, and I
started following the first few modules. I remember working through a few of the
starter courses, but it got difficult fast. There were a few concepts I didn&amp;rsquo;t
immediately understand, and after floundering for a bit, I fell off the learning
wagon. With the braggadocio of a 19-year-old, I cast it aside as something I
wasn&amp;rsquo;t interested in. I didn&amp;rsquo;t take a single CS class in college, and I didn&amp;rsquo;t
pursue more programming until much later.&lt;/p&gt;
&lt;p&gt;Joke&amp;rsquo;s on me — I now do it for my day job, and I really enjoy programming.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In retrospect, I think the main problem was motivation, in multiple ways. It was
during a period of &amp;ldquo;&lt;em&gt;I don&amp;rsquo;t really know what to do, so maybe I&amp;rsquo;ll try to learn
to program, or maybe learn to speak Korean, or what if I got into&amp;hellip;&lt;/em&gt;&amp;rdquo;, where I
was exploring broadly, but without clear goals or much practice learning on my
own. At the time, I didn&amp;rsquo;t even know what I wanted to major in. Because there
wasn&amp;rsquo;t much real interest backing the initial exploration, I backed out when I
ran into some resistance and it required a little more effort, and I instead
moved on to the next thing (spoiler alert: I also didn&amp;rsquo;t learn Korean). I wasn&amp;rsquo;t
very good at putting in the work it takes to learn hard things, and I hadn&amp;rsquo;t had
a lot of practice studying, though I somehow passed (most of) my freshman year
classes without good study skills.&lt;/p&gt;
&lt;p&gt;I think that starting with the broad, amorphous goal of &amp;ldquo;learn to program&amp;rdquo;
didn&amp;rsquo;t include tangible goals that I think would have helped motivate
19-year-old me, like building an actual website, writing a useful app, or making
a basic game. You need the fundamentals to build off of, but ideally you&amp;rsquo;d have
something to build towards. Multiplying numbers for the sake of multiplying
numbers wasn’t enough, and for an autodidactic approach (without a lot of
autodidactic practice), there wasn&amp;rsquo;t strong enough internal motivation towards a
goal to stick with it. Looking at it with more experience, it&amp;rsquo;s also clearly an
impossible goal — you&amp;rsquo;re never really done learning.&lt;/p&gt;
&lt;p&gt;That lack of inspiration wasn&amp;rsquo;t helped by an aversion to putting in the work. At
the time, I hadn&amp;rsquo;t developed strong study skills and had been able to mostly
coast through my freshman year, with a few bumpy general chemistry Cs offset by
my liberal arts As and Bs. I had been able to make it through a lot of my
earlier schoolwork with pretty good grades without learning to seriously study,
and I hadn&amp;rsquo;t yet developed the muscle to work hard on something when it doesn&amp;rsquo;t
immediately come naturally. Thinking like a computer with the rigid rules of a
programming language was a new way of thinking for my brain, and when it
started to get hard, there was that nagging feeling to just move on, and move on
I did.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;One of the unfortunate side effects was that I assumed I wasn&amp;rsquo;t interested in
programming or computer science, even though I had a very, very narrow view of
it and next to no context on what it was actually like.&lt;/p&gt;
&lt;p&gt;Fortunately, I did end up coming back to it much later. When learning the second
time around, a large part of the initial motivation was to make a career shift,
and it was much easier to direct my energy and focus, aided by a little more
maturity and practice learning as well. There was nothing quite like fielding
customer support calls to help motivate me to study after work, and I was able
to stick with it much better. I ran into similar sticking points, but I worked
through them, asking for help and giving it time. I built up the muscles of
putting in the work to keep trying at a problem and sticking with something
difficult until it comes into focus.&lt;/p&gt;
&lt;p&gt;It went much better than my first attempt. As I kept learning, the career shift
motivation gave way to a general interest and enjoyment of both computer science
and learning. It also naturally developed into a positive feedback loop, where I
put in the work to learn more, it got easier to put that work in and learn, and
I enjoyed the process and wanted to keep learning.&lt;/p&gt;
&lt;p&gt;When people ask me about learning to code, I try to think of ways that they can
leverage their interests — maybe if you love cooking, you can learn enough to
build a basic cooking app to scratch your own itch. You&amp;rsquo;ll run into some tough
spots, and having that inspiration can help you get through them, hopefully
until you&amp;rsquo;re well practiced in getting through those tough spots. Better yet, if
you&amp;rsquo;re able to work through it with other people, you can get through those
tough spots together. I&amp;rsquo;ve found that a lot of learning difficult things comes
down to sticking with it and putting in the work to understand; there will
always be tough spots, but you get better at dealing with them.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m glad I stuck with it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This post was inspired by a few thoughts I put together for a Hacker News thread: &amp;ldquo;&lt;a href=&#34;https://news.ycombinator.com/item?id=32074089&#34;&gt;Ask HN: Why did you quit learning programming?&lt;/a&gt;&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Work context, home context</title>
      <link>https://alexanderell.is/posts/context/</link>
      <pubDate>Sun, 10 Jul 2022 12:38:39 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/context/</guid>
      <description>&lt;p&gt;When I usually think about &lt;em&gt;context&lt;/em&gt; in the meta, non-technical part of being a
software engineer, I usually think of context switching and swapping your
brain&amp;rsquo;s focus and working memory from one mode to another, switching from
focused programming to a status meeting or trying to fit in a little coding time
in the 30 minute blocks in between meetings.&lt;/p&gt;
&lt;p&gt;Another aspect of &lt;em&gt;context&lt;/em&gt; I&amp;rsquo;ve been thinking about recently is the separation
of work context and home context. I do best when there are clear distinctions
between the two, where there is a separate context for Work and a separate
context for Life (or at least the important rest of life that isn&amp;rsquo;t about work).
It makes it easier for me to get into work mode, and conversely, it makes it
easier to transition out of work mode into non-work mode. It&amp;rsquo;s like the world&amp;rsquo;s
simplest decision tree:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If I&amp;rsquo;m in my work context, do work things, otherwise, do non-work things.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This &amp;ldquo;in my work context&amp;rdquo; part is a little fuzzy these days, though. It had
vanished for me two and a half years ago. In 2020, my wife and I were living in
a 1-bedroom loft apartment in an urban area, close to the trains that brought me
to the office. It was a smaller space, but we were used to spending a lot of
time outside of it. When the forced WFH happened, it was suddenly not only my
home space, but also my work space, recreation space, and really, my only space.&lt;/p&gt;
&lt;p&gt;Since then, I&amp;rsquo;ve worked mostly remotely, and it has been an interesting exercise
in exploring these contexts and their relationships with my work and life.  I&amp;rsquo;ve
found that physical boundaries help me better build mental boundaries, with one
of the best improvements being when we moved to a 2-bedroom apartment and I was
able to have a separate space to devote to a home office. I could even shut the
door and not see my desk after hours or on the weekends. It&amp;rsquo;s a little funny to
put it into words, but it&amp;rsquo;s really like I was fooling myself with some regressed
object permanence; out of sight, out of mind!&lt;/p&gt;
&lt;p&gt;I would be lying if I said every day was (or is) a good day, though. Especially
during the early pandemic times when I was also facing &lt;a href=&#34;https://alexanderell.is/posts/mscs/&#34;&gt;school context&lt;/a&gt;,
the ratio of good days to bad days fluctuated drastically. Some days it was easy
to get it going, but there were other days where I wouldn&amp;rsquo;t really be able to
get in the groove until the late afternoon, leading to a disruption of my normal
hours and a clobbered work/life balance. Over time, as I better developed my tricks
and habits (and got a second bedroom), the ratio improved, but I&amp;rsquo;ve been
constantly on the lookout for both slippage and ways to improve it further.&lt;/p&gt;
&lt;p&gt;There were a few periods where I had the option of heading back into the office,
and after I did a few times, I was struck by how much it helped reinforce those
boundaries. Although half of my days were spent catching up and drinking
coffees, when it came to sitting down and getting some focused work done, it was
easy. We were at work, so of course I would just knock out some work! I&amp;rsquo;m also
fortunate enough to be able to slip into a focused mode with a pair of noise
canceling headphones and some good music, even with a cluttered visual field,
allowing even a noisy open office to exist outside of my little pond of
stillness and focus. Even though the environment was much busier than my private
office at home, it was the act of being in that environment itself that made it
easier for it all to flow.&lt;/p&gt;
&lt;p&gt;More importantly, though, I found that I was better able to leave work at work.
I would log off, close the laptop, maybe leave it there, and walk out of the
building. My commute was only around 30 minutes or so, a mix of walking and easy
trains, letting my brain wander and gracefully transition away from the worries
of work. I would sometimes still think about it, but there was a much cleaner
break.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Oh, work? Yeah, I&amp;rsquo;ll deal with that when I&amp;rsquo;m back tomorrow.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve since left that office job and started working fully remotely. Mindful of
these mental tricks that help me effectively strike a good balance, I&amp;rsquo;ve
deliberately focused on building up the physical barriers and routines that
help. I have a dedicated bedroom office, strict hours, commute-like dog walks
before and after work to clear the cobwebs, and a silly but strict policy of
putting my work laptop away immediately after my workday is done.&lt;/p&gt;
&lt;p&gt;For an extra step, I&amp;rsquo;ve recently started renting a private office in a local
coworking space. It has been a great addition so far, giving me that additional
physical separation of a place for work and a place for the rest of life. With a
clearly dedicated space, people to talk to, and the extra physical context, it
has reminded me of just how helpful the separation can be. Maybe someday I&amp;rsquo;ll
be out in the countryside with a separate work/office shack I can retreat to
during my work day, but for now, the private office is a wonderful balance for
our current city living, and I&amp;rsquo;m excited to have that space and be a part of the
community.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m always envious of the people that don&amp;rsquo;t need these extra steps to
effectively strike healthy balances, but I know it&amp;rsquo;s harder for me. It has made
me appreciate the wider range of working styles, and it has made me more
skeptical of broad stroke declarations of How You Should Work, with the nuances
showing themselves in the different experiences I&amp;rsquo;ve seen and had myself.&lt;/p&gt;
&lt;p&gt;A big part of the game for me is to understand how my brain works, then work
with it. For me, the physical context switching helps my brain, and I&amp;rsquo;ll
be leveraging that as much as I can.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to break the Envoy CI at head</title>
      <link>https://alexanderell.is/posts/break-envoy/</link>
      <pubDate>Sun, 03 Jul 2022 18:16:17 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/break-envoy/</guid>
      <description>&lt;p&gt;I like to talk about small mistakes I&amp;rsquo;ve made. I&amp;rsquo;m a big proponent of
psychological safety, and as a champion for it, I think it&amp;rsquo;s very important to
talk about times you&amp;rsquo;ve slipped up or done something dumb. We all make mistakes,
and there are times when you feel silly for doing something.&lt;/p&gt;
&lt;p&gt;This is one of those times.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://alexanderell.is/posts/my-longest-running-pr&#34;&gt;I recently merged a large pull request in Envoy that required a lot of work&lt;/a&gt;.
It was so satisfying to see the approvals come in, and that final moment where
the &lt;a href=&#34;https://github.com/envoyproxy/envoy/pull/20281&#34;&gt;PR&lt;/a&gt; finally merged was so
gratifying after many months, many delays, and many revisions.&lt;/p&gt;
&lt;p&gt;It &lt;em&gt;was&lt;/em&gt;, at least, until I got a few GitHub notifications:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@AlexanderEllis we&amp;rsquo;re reverting this because it broke CI on main. Can you
figure out what broke and re-submit the PR with a fix? The easiest-to-review
way to do that is to revert this revert, then add another commit with whatever
fix needs to be made.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ah, that satisfying feeling, quickly replaced by &amp;ldquo;oh shit I broke the build for
a big public project&amp;rdquo;, the satisfaction vanishing into the afternoon breeze. Not
the end of the world, but I hate that feeling, especially as it meant other PRs
were blocked.&lt;/p&gt;
&lt;h3 id=&#34;the-problem&#34;&gt;The problem&lt;/h3&gt;
&lt;p&gt;Scoping out the error was illuminating, but mostly because I&amp;rsquo;ve been
desensitized to this sort of error message.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;...
[ RUN      ] GrpcAccessLoggerCacheImplTest.LoggerCreationResourceAttributes
unknown file: Failure
C++ exception with description &amp;#34;Protobuf message (type opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest reason INVALID_ARGUMENT:(resource_logs.instrumentation_library_logs[0]) logs: Cannot find field.) has unknown fields&amp;#34; thrown in the test body.
test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc:51: Failure
Actual function call count doesn&amp;#39;t match EXPECT_CALL(*async_client, startRaw(_, _, _, _))...
         Expected: to be called once
           Actual: never called - unsatisfied and active
Stack trace:
  0xf9f9ff: testing::internal::GoogleTestFailureReporter::ReportFailure()
  0xfa25ca: testing::internal::UntypedFunctionMockerBase::VerifyAndClearExpectationsLocked()
  0x6bab7e: testing::internal::FunctionMocker&amp;lt;&amp;gt;::~FunctionMocker()
  0x6bac9d: Envoy::Grpc::MockAsyncClient::~MockAsyncClient()
  0x6bacbe: Envoy::Grpc::MockAsyncClient::~MockAsyncClient()
  0x6b5961: Envoy::Extensions::AccessLoggers::Common::GrpcAccessLogger&amp;lt;&amp;gt;::~GrpcAccessLogger()
  0x6b6b81: Envoy::Extensions::AccessLoggers::Common::GrpcAccessLoggerCache&amp;lt;&amp;gt;::ThreadLocalCache::~ThreadLocalCache()
  0x90b59a: Envoy::ThreadLocal::MockInstance::SlotImpl::~SlotImpl()
  0x90b5fe: Envoy::ThreadLocal::MockInstance::SlotImpl::~SlotImpl()
  0x6b41aa: Envoy::Extensions::AccessLoggers::OpenTelemetry::(anonymous namespace)::GrpcAccessLoggerCacheImplTest::~GrpcAccessLoggerCacheImplTest()
  0x6b425e: Envoy::Extensions::AccessLoggers::OpenTelemetry::(anonymous namespace)::GrpcAccessLoggerCacheImplTest_LoggerCreationResourceAttributes_Test::~GrpcAccessLoggerCacheImplTest_LoggerCreationResourceAttributes_Test()
  0xfb49dc: testing::internal::HandleExceptionsInMethodIfSupported&amp;lt;&amp;gt;()
  0xfb5894: testing::TestInfo::Run()
  0xfb65e9: testing::TestSuite::Run()
... Google Test internal frames ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This means that an exception was thrown because there was an unknown field,
the &lt;code&gt;log&lt;/code&gt; field, in a &lt;code&gt;resource_logs.instrumentation_library_logs&lt;/code&gt; proto.
Because that exception was thrown, this meant that the gmock
expectation on line 51 of the &lt;code&gt;async_client.startRaw&lt;/code&gt; function being called was
&lt;em&gt;not&lt;/em&gt; being fulfilled, as the exception stopped everything immediately.&lt;/p&gt;
&lt;p&gt;Much like the actual function call, I was also very unsatisfied.&lt;/p&gt;
&lt;p&gt;This test in question,
&lt;code&gt;test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc&lt;/code&gt;,
was parsing YAML strings as protos to ensure the right fields were being
populated correctly, with something like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C++&#34; data-lang=&#34;C++&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; std&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;string expected_message_yaml &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; R&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;EOF(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  resource_logs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    instrumentation_library_logs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; log:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; severity_text: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;test-severity-text&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )EOF&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;opentelemetry&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;proto&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;collector&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;logs&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;v1&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;ExportLogsServiceRequest expected_message;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TestUtility&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;loadFromYaml(expected_message_yaml, expected_message);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ... more C++ goodness that verifies that the expected message is what shows up
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The error above meant that it was running into trouble when parsing this YAML,
purportedly because the &lt;code&gt;log&lt;/code&gt; field didn&amp;rsquo;t exist.&lt;/p&gt;
&lt;h3 id=&#34;the-diagnosis&#34;&gt;The diagnosis&lt;/h3&gt;
&lt;p&gt;This area of the code was actually only tangentially related to the changes I
was making. I was adding a new OpenTelemetry &lt;em&gt;tracing&lt;/em&gt; extension, but this error
was in the OpenTelemetry &lt;em&gt;access logger&lt;/em&gt;.  Although it was a separate extension
altogether, it immediately rang a bell as a suspicious character I had seen
before.&lt;/p&gt;
&lt;p&gt;As part of the PR, I had bumped the version of the
&lt;a href=&#34;https://github.com/open-telemetry/opentelemetry-proto&#34;&gt;OpenTelemetry Proto library&lt;/a&gt;
that Envoy uses. One of the included changes in this bump was a breaking
change that renamed the &lt;code&gt;log&lt;/code&gt; field in
&lt;code&gt;resouce_logs.instrumentation_library_logs&lt;/code&gt; to &lt;code&gt;log_records&lt;/code&gt;.
I had bumped the version, and I had already done
&lt;a href=&#34;https://github.com/envoyproxy/envoy/pull/20281/commits/4057f4f54d8e8679cf779f87af4432aacbab5ada&#34;&gt;a little work to deal with the new field name&lt;/a&gt;,
even though I was doing nothing related to logging.&lt;/p&gt;
&lt;p&gt;Unfortunately for Envoy&amp;rsquo;s CI, with hilariously bad timing, around 6 hours before
my multi-month PR was merged, a different PR had added a new test with the &lt;code&gt;log&lt;/code&gt;
field in a YAML string that it parsed. I hadn&amp;rsquo;t pulled that commit into my
branch, and when my PR was merged, this new test immediately started throwing
exceptions when it was being run, as this &lt;code&gt;log&lt;/code&gt; field no longer existed with the
new version of the library.&lt;/p&gt;
&lt;p&gt;Luckily, this meant that it was only
&lt;a href=&#34;https://github.com/envoyproxy/envoy/pull/21842/files&#34;&gt;a quick one line forward fix&lt;/a&gt;
to get things back in business.&lt;/p&gt;
&lt;h3 id=&#34;happy-little-accidents&#34;&gt;Happy little accidents&lt;/h3&gt;
&lt;p&gt;These silly mistakes are always helpful, if only for little reminders to
yourself down the line. If you&amp;rsquo;re looking for a few recommendations, this is a
good little reminder about a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Be more careful about merging main (rookie mistake, and I blame myself for
getting out of practice after 3.5 years of not using git for work)&lt;/li&gt;
&lt;li&gt;Be careful about unstable library upgrades&lt;/li&gt;
&lt;li&gt;Decouple library upgrades from other functional changes and avoid large PRs&lt;/li&gt;
&lt;li&gt;As I mention in that other post, do everything you can to not have
multi-month PRs!&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;p&gt;May your builds be ever green and your tests be ever passing.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Long running pull requests</title>
      <link>https://alexanderell.is/posts/my-longest-running-pr/</link>
      <pubDate>Sun, 26 Jun 2022 11:45:27 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/my-longest-running-pr/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s nothing quite like merging a pull request that has been pending for
months. It&amp;rsquo;s so satisfying.&lt;/p&gt;
&lt;p&gt;At Google, they have little badges for your internal user profile. One of the
ones you can get celebrates the longest time delta in between creating a change
and submitting it. For example, if you create a PR on August 1st and submit it
on September 1st, you&amp;rsquo;d probably get the 1-month PR badge.&lt;/p&gt;
&lt;p&gt;I think I would have broken my record for a recent pull request I submitted. As
part of my &lt;a href=&#34;https://alexanderell.is/posts/mscs&#34;&gt;MSCS thesis work&lt;/a&gt;, I added native
&lt;a href=&#34;https://opentelemetry.io&#34;&gt;OpenTelemetry&lt;/a&gt; tracing to
&lt;a href=&#34;https://www.envoyproxy.io&#34;&gt;Envoy&lt;/a&gt;. Even before the PR, there was a ton of work
to write a proposal, chat with folks, ramp up on Envoy tracing internals, ramp
up on OpenTelemetry, figure out the best way to do it, write a prototype, do
some testing, get some metrics, write the actual thesis (a casual sprint
marathon), defend it, and finally, polish the code to production level and write
some solid tests. By nature of it being a side project, the work unfortunately
took a back seat to other priorities, like day-job work that pays the bills.&lt;/p&gt;
&lt;p&gt;I finally
&lt;a href=&#34;https://github.com/envoyproxy/envoy/pull/20281&#34;&gt;opened a PR on Mar 9, and it was finally merged on June 22&lt;/a&gt;.
105 days, 15 weeks, or 3 months and 13 days.&lt;/p&gt;
&lt;p&gt;If your brain works anything like mine, a pending pull request lives in the back
of your head, stealing cycles that you&amp;rsquo;d otherwise spend thinking about other
things. Worse yet is when you don&amp;rsquo;t have the time to work on it — comments and
reviews slip from &amp;ldquo;I need to do this&amp;rdquo; territory into &amp;ldquo;I&amp;rsquo;m embarrassed I haven&amp;rsquo;t
done this yet&amp;rdquo; territory. Going back to the code feels like dusting off old
library books — I made notes to myself, but of course with the context I had in
my head at the time.&lt;/p&gt;
&lt;p&gt;I also switched jobs in that timeframe. With everything involved taking up more
time (day job, interviewing, wrapping up day job, ramping up at new day job),
the backpressure was real, and the 20% project open source work packets got
dropped.&lt;/p&gt;
&lt;p&gt;In case you&amp;rsquo;re new to software, the most obvious problem here is that you never
want to be in a position where your PR takes months. It should be smaller
and self-contained, making the job of reviewing it easy. You should respond to
comments promptly and iterate quickly to avoid losing your context. It&amp;rsquo;s much
better to work consistently, rather than bursting code with weeks of delay in
between. Or, to put it another way, do as I say, not as I do!&lt;/p&gt;
&lt;p&gt;This PR was the majority of the work to get this feature in place, and future
PRs will be smaller, more self-contained, and easier to shepherd through the
review process. I&amp;rsquo;m looking forward to writing those, but I&amp;rsquo;m just glad to have
this one all set.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;As a final note, merging the PR actually broke Envoy at head due to a subtle
issue with an updated field in parsed YAML in a test. Nothing like a quick
&lt;a href=&#34;https://github.com/envoyproxy/envoy/pull/21842&#34;&gt;forward fix&lt;/a&gt; to keep you on
your toes!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How far behind a plane is its noise?</title>
      <link>https://alexanderell.is/posts/plane-noise/</link>
      <pubDate>Sun, 12 Jun 2022 10:03:10 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/plane-noise/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re ever near a relatively low flying airplane, you may have noticed that
it sounds like it&amp;rsquo;s in a different spot in the sky than where it actually is.
You can hear the loud engines, and your ears tell you it should be in one place,
but your eyes tell you it&amp;rsquo;s clearly well ahead of its noise.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a funny visual trick, and, living near the Boston airport flight path, I
was thinking the other day about how it works.&lt;/p&gt;
&lt;h3 id=&#34;what-does-it-mean-to-be-behind&#34;&gt;What does it mean to be &amp;ldquo;behind&amp;rdquo;?&lt;/h3&gt;
&lt;p&gt;The first key point is that the concept of the noise being &amp;ldquo;behind&amp;rdquo; the plane
relies on you, as an observer, trying to locate the plane. These planes are
generating noise from their engines constantly, and that noise spreads out from
the engines. From the plane&amp;rsquo;s point of view, there&amp;rsquo;s no real &amp;ldquo;behind&amp;rdquo; — the
engines are just constantly making noise&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;re a little farther away from the plane, it gets a little more
interesting. Because noise and light travel at different speeds through air,
they can look like they&amp;rsquo;re out of sync. Let&amp;rsquo;s say you&amp;rsquo;re observing a flying
plane. At some instant, that plane&amp;rsquo;s loud engines make some noise, which travels
through the air like a ripple on a pond.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;diagram-1.png&#34; alt=&#34;Diagram showing a plane that makes noise like a ripple&#34;&gt;&lt;/p&gt;
&lt;p&gt;A small amount of time later, the sound wave will also have continued on its
journey. The plane will also have continued on its journey, and it will be
creating new sound waves. For now, let&amp;rsquo;s just think about the sound that the
plane created at that earlier point in time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;diagram-2.png&#34; alt=&#34;Diagram showing the plane&amp;amp;rsquo;s noise spreading like a ripple&#34;&gt;&lt;/p&gt;
&lt;p&gt;The plane is now some distance from where it was when it created that noise —
marked by the semi-transparent plane in the diagram. The sound hasn&amp;rsquo;t reached
you yet, but the effect is already in motion.&lt;/p&gt;
&lt;p&gt;This will continue as the plane continues to fly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;diagram-3.png&#34; alt=&#34;Diagram showing the plane&amp;amp;rsquo;s noise spreading like a ripple&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;diagram-4.png&#34; alt=&#34;Diagram showing the plane&amp;amp;rsquo;s noise spreading like a ripple&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;diagram-5.png&#34; alt=&#34;Diagram showing the plane&amp;amp;rsquo;s noise spreading like a ripple&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;diagram-6.png&#34; alt=&#34;Diagram showing the plane&amp;amp;rsquo;s noise spreading like a ripple, finally reaching an observer&#34;&gt;&lt;/p&gt;
&lt;p&gt;Finally, the sound wave from the original spot reaches you. By this time, the
plane has zoomed ahead, and it&amp;rsquo;s far from its original position. Because we&amp;rsquo;re
so used to the things we hear being where we see them, it looks to our ears like
the plane is in the original position, but it has clearly moved on!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;diagram-7.png&#34; alt=&#34;Diagram showing the difference between the plane&amp;amp;rsquo;s current position and where it was when it made the original noise&#34;&gt;&lt;/p&gt;
&lt;p&gt;As the plane continues to fly, we as observers hear the ripples that the plane
created in the past. Because these sound waves arrive continuously from where
the plane used to be, it looks as if the plane&amp;rsquo;s sound is actually behind it.&lt;/p&gt;
&lt;p&gt;Visualizing this with the pictures above, you can see that a few things would affect this effect:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The speed of the plane&lt;/li&gt;
&lt;li&gt;The speed of the sound (and how loud it is)&lt;/li&gt;
&lt;li&gt;The distance from the observer to the plane&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;p&gt;If the plane was moving very slowly, it wouldn&amp;rsquo;t outpace its sound by much. If
the speed of sound was faster, it would get to us faster, leading to less of an
effect. If the noise wasn&amp;rsquo;t loud enough or the plane was too far away, the noise
would also dissipate into the air before it got to us (like planes at cruising
altitude). If the distance between us and the plane was smaller, there wouldn&amp;rsquo;t
be as much of a dramatic difference between the two speeds.&lt;/p&gt;
&lt;p&gt;Luckily for us, planes move fast, they make a lot of noise, sound is relatively
slow (compared to light), and planes are usually far enough away.&lt;/p&gt;
&lt;p&gt;What a funny trick!&lt;/p&gt;
&lt;h3 id=&#34;light-versus-sound&#34;&gt;Light versus sound&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re able to see the plane because of the light reflecting off of it, and we&amp;rsquo;re
able to hear the plane because of the noise from the engine.  Light travels
really fast, so we can assume that the plane is exactly where it looks like it
is. Because the light and the sound travel the distance to us at different
speeds, there ends up being this funny gap between the two. Because the plane is
moving fast and light gets to us very, very quickly, by the time the sound
finally makes it to us, the plane has moved on.&lt;/p&gt;
&lt;p&gt;Part of the reason this ends up being such a funny feeling is
because our ears are usually so good at
&lt;a href=&#34;https://en.wikipedia.org/wiki/Human_echolocation&#34;&gt;visualizing where something is from its sound&lt;/a&gt;. If you&amp;rsquo;re able to, try having someone walk around the room and snap their
fingers while you have your eyes closed — even without the visual clues from
your eyes, your ears are working overtime to help you &amp;ldquo;see&amp;rdquo; where they are in 3D
space. You could imagine this being very helpful evolutionarily — when I hear
the prey I&amp;rsquo;m hunting and am able to visualize where it is, it makes it that much
easier to see it and get it.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Even though this phenomenon is the easiest to visualize with sound since the
speed gap is wide, it also shows up with light, though it&amp;rsquo;s a little weirder. If
something is moving fast enough and the distance between us and the object is
large enough, there can be a gap between where it looks like the object is and
where it actually is.&lt;/p&gt;
&lt;p&gt;For example, the sun isn&amp;rsquo;t where the sun looks like it is — it takes about 8
minutes for the light to get to us, so we&amp;rsquo;re seeing the sun where it used to be,
8 minutes ago. In reality, much like our plane from earlier, the sun has moved
on. This one is a little harder to visualize, because it decouples the light
from something and that something&amp;rsquo;s position, but it&amp;rsquo;s the same exact effect,
though without the additional ability to easily verify where something is. This
is a neat part of astrophysics, and it&amp;rsquo;s a little more mind bending — if we&amp;rsquo;re
pretty used to things being where they sound like they are, then we&amp;rsquo;re very,
very used to things being where they look like they are!&lt;/p&gt;
&lt;h3 id=&#34;napkin-math&#34;&gt;Napkin math&lt;/h3&gt;
&lt;p&gt;We can easily calculate how far the sound is behind the plane with a few
approximations.&lt;/p&gt;
&lt;p&gt;Like any good physicist, we&amp;rsquo;ll fudge the numbers with some reasonable
approximations. Let&amp;rsquo;s say that the speed of sound is constant in the air between
us and the plane (it&amp;rsquo;s not). Let&amp;rsquo;s say that the speed of light is instantaneous
(it&amp;rsquo;s not), so the plane is always where it looks like it is (it slightly
isn&amp;rsquo;t). Let&amp;rsquo;s assume the speed of the plane is constant (it probably isn&amp;rsquo;t), and
let&amp;rsquo;s assume it&amp;rsquo;s flying in a straight line through space (it probably isn&amp;rsquo;t).
We can also look at right angles to help visualize the math easier.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;napkin-math-1.png&#34; alt=&#34;Diagram showing x, the distance between the plane and its original spot, and y, the distance to the observer&#34;&gt;&lt;/p&gt;
&lt;p&gt;The math is pretty simple: in the time it takes for the sound to reach us, how
far does the plane go? We can start with some basic values for everything. Let&amp;rsquo;s
say sound travels at exactly 343 meters per second. Let&amp;rsquo;s say the plane is at a
usual landing speed, 265 kilometers per hour, which is around 73.6 meters per
second. Let&amp;rsquo;s say the observer is 10km from the plane (you can make your own
triangles from altitude and GPS coordinates if you want).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;napkin-math-2.png&#34; alt=&#34;Diagram showing solving for x&#34;&gt;&lt;/p&gt;
&lt;p&gt;With this, it looks like the sound would be around 2.1km behind the plane —
definitely enough to notice!&lt;/p&gt;
&lt;p&gt;How could you estimate this quickly when you&amp;rsquo;re out and about? One easy way to
do it would be to keep your eyes locked on a single position where the plane is
at one instant, then, keeping your eyes on that same spot, count seconds
mentally until the sound &amp;ldquo;looks&amp;rdquo; like it has also reached that spot. Multiply
that by some rough speed of the airplane, maybe 100m/s or so (around 360kph or
224mph, which may be a good guess for a landing/taking off aircraft&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;), and
that will tell you roughly how far behind the sound is. Multiply it by the speed
of sound (350m/s or so for napkin math), and that will tell you roughly how far
away you are from the plane.&lt;/p&gt;
&lt;p&gt;This means that if a plane takes 15 seconds for its sound to catch up, it will
&amp;ldquo;look&amp;rdquo; like the plane is 1.5km ahead of its noise, and you can also tell you&amp;rsquo;re
around 5km away. Pretty neat!&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Mach speeds notwithstanding — although flying faster than the speed of
sound doesn&amp;rsquo;t change the effect we&amp;rsquo;re talking about here, it does get into
all sorts of other interesting sound questions.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;As a quick aside, dogs tilt their heads when listening intently, as it
allows them to better echolocate the source of the noise.
&lt;a href=&#34;karl-head-tilt.jpeg&#34;&gt;Here&amp;rsquo;s some dog tax of this in action.&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Details left as an exercise to the aeronautically-minded reader.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>I only care about the helpful notifications, not the promotional ones</title>
      <link>https://alexanderell.is/posts/sneaking-notifications/</link>
      <pubDate>Sun, 05 Jun 2022 10:54:51 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/sneaking-notifications/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s a scenario. First, you download an app for a service that has some sort
of in-person component, like picking up an order or meeting a driver at the
curb. When using that app for the goal, be it that order or that meeting, it&amp;rsquo;s
very helpful to have notifications, since they give you the current status
and it can be important to coordinate.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;d like to get a notification for when your order is ready, since that takes
away the need for you to repeatedly poll the app and refresh constantly. This
makes sense.&lt;/p&gt;
&lt;p&gt;You get your notification, you meet your driver or pick up your order, and all&amp;rsquo;s
well. Those notifications helped you coordinate during the transaction, and
hopefully you were able to pick up your order before it got too cold.  Maybe you
get a follow up notification to rate your experience, but other than that, these
notifications were helpful. You probably don&amp;rsquo;t even think about disabling them,
since maybe the next time you use the app you&amp;rsquo;ll want those helpful pings again.&lt;/p&gt;
&lt;p&gt;Now it&amp;rsquo;s a week later, and you haven&amp;rsquo;t used the app since. Your phone lights up
with a notification from the same app.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;sweetgreen.PNG&#34; alt=&#34;Screenshot of a SweetGreen marketing notification sent outside of any app interaction&#34;&gt;&lt;/p&gt;
&lt;p&gt;Ahh, tricky. You want notifications during the transaction, the goal sequence,
the thing you downloaded the app for. They&amp;rsquo;ve got you to enable notifications,
since they can be helpful, but now they&amp;rsquo;ve co-opted that notification consent to
send you marketing notifications.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t want to be notified that there are only 9 days left to try Devin
Booker&amp;rsquo;s salad (no offense to Devin Booker). I want to be notified when there
are things that need my attention in real time during this business transaction
we&amp;rsquo;re engaged in.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;What are you going to do? The only control available (for me, at the time of
writing) is to disable notifications at the app level. That seems coarse
grained, especially since I do want some of those notifications. Am I really
going to enable and disable them every time I use the app? Maybe, but that&amp;rsquo;s
additional toil, and maybe I won&amp;rsquo;t always remember to. It would be nice if there were finer grained or more accessible controls, like
if there was an option when long-pressing the app on the home screen to quickly
enable or disable notifications.&lt;/p&gt;
&lt;p&gt;Better yet, it would be great to have some sort of &amp;ldquo;disable marketing
notifications&amp;rdquo; inside the app. Great for us users, but not as great for the
companies that are knowingly using this channel for both useful and unuseful
notifications and their business engagement metrics. I say &amp;ldquo;knowingly&amp;rdquo;, because
it&amp;rsquo;s pretty clear when you look at something like Starbucks&amp;rsquo; notifications
options:&lt;/p&gt;
&lt;br&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Dear &lt;a href=&#34;https://twitter.com/Starbucks?ref_src=twsrc%5Etfw&#34;&gt;@Starbucks&lt;/a&gt;, this is actually very simple: if you put promotions and order status into the same notifications bucket then you will not be allowed to deliver either. &lt;a href=&#34;https://t.co/6t64veJyUQ&#34;&gt;pic.twitter.com/6t64veJyUQ&lt;/a&gt;&lt;/p&gt;&amp;mdash; Malte Ubl (@cramforce) &lt;a href=&#34;https://twitter.com/cramforce/status/1529866357763821583?ref_src=twsrc%5Etfw&#34;&gt;May 26, 2022&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;br&gt;
&lt;p&gt;Tricky. The only way to disable notifications for promotions is to also disable
notifications for order status, the latter being the actual useful ones. By
removing the user&amp;rsquo;s explicit choice between the two, they&amp;rsquo;re grouping the
helpful with the unhelpful, and I&amp;rsquo;m sure they&amp;rsquo;re getting that many more
engagements because of it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Is it a disconnect between user intent and the app&amp;rsquo;s actual behavior? By
not giving the user an explicit choice, they naively accept that &amp;ldquo;yes to
all notifications&amp;rdquo; option, the only option, when really I think we all
know what notifications are the most helpful to the user. It&amp;rsquo;s like pretending
that because you want all of your mail delivered, you also want junk mail
delivered. That&amp;rsquo;s why I find the Starbucks example so interesting — they&amp;rsquo;re
clearly showing the strict pairing of the two!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll be keeping an eye out for this channel re-use. For now I&amp;rsquo;ll take on the
toil to disable them as needed, but it would be nice if we didn&amp;rsquo;t have to.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Playing with a 3D representation of RGB color space</title>
      <link>https://alexanderell.is/posts/rgb-color-space/</link>
      <pubDate>Sat, 28 May 2022 12:29:44 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/rgb-color-space/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m working on a color picking/finding game, and part of it will be asking the
user to pick a color from a range of colors. This got me thinking: what are some
ways we can represent RGB color space?&lt;/p&gt;
&lt;h2 id=&#34;rgb&#34;&gt;RGB&lt;/h2&gt;
&lt;p&gt;One way to think of colors, especially colors on computers, is as a combination
of three different primary colors: &lt;strong&gt;R&lt;/strong&gt;ed, &lt;strong&gt;G&lt;/strong&gt;reen, and &lt;strong&gt;B&lt;/strong&gt;lue. If we say
how much of each color we want, we can build a color from the values. For
example, maybe we want all Red and no Green or Blue - we could represent this as
(100%, 0%, 0%), corresponding to (R, G, B). That would look like this:&lt;/p&gt;
&lt;div style=&#34;height: 100px; width: 100px; background-color: #ff0000&#34;&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;That&amp;rsquo;s an OK red, but it gets more interesting when we mix in some other colors.
For instance, here&amp;rsquo;s 100% red, 33% green, and 33% blue:&lt;/p&gt;
&lt;div style=&#34;height: 100px; width: 100px; background-color: #ff5656&#34;&gt;&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;Now we&amp;rsquo;re talking!&lt;/p&gt;
&lt;h3 id=&#34;2d-but-with-a-spectrum&#34;&gt;2D, but with a spectrum&lt;/h3&gt;
&lt;p&gt;If you Google &amp;ldquo;color picker&amp;rdquo;, you get a widget that provides you with
a spectrum of colors to pick from. I think these are pretty intuitive, since
you&amp;rsquo;re able to see what colors are close and similar to your colors. That
being said, it&amp;rsquo;s a little unintuitive how the actual values map to the layout
on the grid - try clicking and dragging left and right, and see what the RGB values do.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;google-picker.png&#34; alt=&#34;Screenshot of Google&amp;amp;rsquo;s default color picker&#34;&gt;
Give it a shot &lt;a href=&#34;https://www.google.com/search?q=color+picker&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The y-axis looks like it&amp;rsquo;s red, as going up and down on the far left and far
right only changes the red value. But, go to the middle and go up and down, and
all three values change. Put your picker in the middle, then change the spectrum
from red to blue - you can see there are discrete sections where only one value
or another is increasing. The display is intuitive, but the numbers aren&amp;rsquo;t!&lt;/p&gt;
&lt;h2 id=&#34;3d-rgb-color-space&#34;&gt;3D RGB color space&lt;/h2&gt;
&lt;p&gt;Because each color is composed of those three separate values, you could imagine
it as if it were a 3D chart, with one axis for red, one axis for green, and one
axis for blue. As you go up to 100% for each axis, the color changes
accordingly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://upload.wikimedia.org/wikipedia/commons/8/83/RGB_Cube_Show_lowgamma_cutout_b.png&#34; alt=&#34;Color cube, with each color on a different axis&#34;&gt;
SharkD, CC BY-SA 3.0 &lt;a href=&#34;https://creativecommons.org/licenses/by-sa/3.0&#34;&gt;https://creativecommons.org/licenses/by-sa/3.0&lt;/a&gt;, via Wikimedia Commons&lt;/p&gt;
&lt;p&gt;You could, if you wanted to, actually &lt;em&gt;make&lt;/em&gt; one of these cubes. That&amp;rsquo;s what
&lt;a href=&#34;https://taubaauerbach.com/view.php?id=286&amp;amp;alt=2945&#34;&gt;Tauba Auerbach did in the RGB ColorspaceAtlas&lt;/a&gt;, where the pages in a
book correspond to a slice of that cube, so that every color in the entire space
is printed. Pretty neat!&lt;/p&gt;
&lt;h3 id=&#34;how-do-we-see-this-represented&#34;&gt;How do we see this represented?&lt;/h3&gt;
&lt;p&gt;Oftentimes when working with computers and colors, you&amp;rsquo;ll want to pick a color
from a selection of colors. An interesting question here is how the colors are
represented — on one unhelpful end, we could just type in the color codes and
see the color, while on the other end, we&amp;rsquo;d somehow fly through the 3D color
space to find the right one. An interesting consideration is how well the
representation lets you compare the color you&amp;rsquo;ve chosen with its colorful
neighbors. If you&amp;rsquo;re just typing in an exact number, you won&amp;rsquo;t have any context
of the colors around it. If you&amp;rsquo;re selecting from a spectrum, you&amp;rsquo;d have much
more context, though this gets interesting when you think in 3D.&lt;/p&gt;
&lt;p&gt;As a side note, we usually talk about each color ranging from 0 to 255, with
each color represented by 8 bits. I&amp;rsquo;ll use percentages below, but keep that in
mind for the actual code and color pickers.&lt;/p&gt;
&lt;h3 id=&#34;exploring-in-3d&#34;&gt;Exploring in 3D&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s start with what looks like a very color picker, where you can pick a value
for each color and see how it changes. As you go through, you can also visualize
in the 3D space below, where one axis is for red, one is for green, and one is
for blue. I&amp;rsquo;ll let you figure out which is which! Feel free to change the sliders
and see how the color changes.&lt;/p&gt;
&lt;p&gt;For an extra bonus, try enabling DVD screensaver mode below — you can still mess with the
sliders while it&amp;rsquo;s running.&lt;/p&gt;
&lt;div style=&#34;display: flex;&#34;&gt;
  &lt;div id=&#34;slider-select&#34; style=&#34;height: 100px; width: 100px; background-color: #7F7F7F&#34;&gt;&lt;/div&gt;
  &lt;div style=&#34;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-left: 20px;&#34;&gt;
    &lt;div&gt;
      &lt;input type=&#34;range&#34; id=&#34;red&#34; name=&#34;red&#34;
            min=&#34;0&#34; max=&#34;100&#34;&gt;
      &lt;label for=&#34;red&#34;&gt;Red: &lt;span id=&#34;red-label&#34;&gt;50&lt;/span&gt;%&lt;/label&gt;
    &lt;/div&gt;
    &lt;div&gt;
      &lt;input type=&#34;range&#34; id=&#34;green&#34; name=&#34;green&#34;
            min=&#34;0&#34; max=&#34;100&#34;&gt;
      &lt;label for=&#34;green&#34;&gt;Green: &lt;span id=&#34;green-label&#34;&gt;50&lt;/span&gt;%&lt;/label&gt;
    &lt;/div&gt;
    &lt;div&gt;
      &lt;input type=&#34;range&#34; id=&#34;blue&#34; name=&#34;blue&#34;
            min=&#34;0&#34; max=&#34;100&#34;&gt;
      &lt;label for=&#34;blue&#34;&gt;Blue: &lt;span id=&#34;blue-label&#34;&gt;50&lt;/span&gt;%&lt;/label&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div id=&#34;x3d-container&#34;&gt;&lt;/div&gt;
&lt;fieldset&gt;
    &lt;legend&gt;DVD screensaver mode:&lt;/legend&gt;
    &lt;div&gt;
      &lt;input type=&#34;radio&#34; id=&#34;dvd-off&#34; name=&#34;drone&#34; value=&#34;off&#34; checked onclick=&#34;handleRadioChange(this)&#34;&gt;
      &lt;label for=&#34;dvd-off&#34;&gt;Off&lt;/label&gt;
      &lt;input type=&#34;radio&#34; id=&#34;dvd-on&#34; name=&#34;drone&#34; value=&#34;on&#34;
             onclick=&#34;handleRadioChange(this)&#34;&gt;
      &lt;label for=&#34;dvd-on&#34;&gt;On&lt;/label&gt;
    &lt;/div&gt;
&lt;/fieldset&gt;
&lt;p&gt;&lt;noscript&gt; Unfortunately, JavaScript is needed to run this little game. The only
JS running on my site is for the games (no tracking, etc) if you want to enable
it. If not, I totally understand :) &lt;/noscript&gt;&lt;/p&gt;
&lt;script src=&#34;https://x3dom.org/download/1.7/x3dom.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://d3js.org/d3.v4.0.0-alpha.28.min.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;d3-x3dom-axis.min.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;color-space.js&#34;&gt;&lt;/script&gt;
&lt;br&gt;
&lt;p&gt;Not bad! It&amp;rsquo;s almost like an ant crawling through the inside of that cube above,
reporting back the color it finds at each position.&lt;/p&gt;
&lt;p&gt;But I think there are some key drawbacks with this single-color visualization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s hard to visualize what changing a value does to the color, and you can&amp;rsquo;t
see the neighbors before you select the color.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re only able to see the exact color at a time, so searching is more
difficult.&lt;/li&gt;
&lt;li&gt;You have three axes to change, but you&amp;rsquo;re only able to do one slider at a
time.&lt;/li&gt;
&lt;li&gt;DVD mode repeats itself frequently&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;I wonder what this could look like if you had a plane of color across two of the
axes, then adjusted the third axis? It would kind of be like fluidly switching
between pages in the book above.&lt;/p&gt;
&lt;p&gt;This is already rambling enough, so I&amp;rsquo;ll have to explore that another time :)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;You can see the source code &lt;a href=&#34;color-space.js&#34;&gt;here&lt;/a&gt;, though it&amp;rsquo;s very rough
around the edges. It&amp;rsquo;s built with D3 and X3DOM, though it was mostly scraped
together from examples.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Wikipedia Over WhatsApp</title>
      <link>https://alexanderell.is/posts/wikipedia-over-whatsapp/</link>
      <pubDate>Sat, 21 May 2022 17:37:15 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/wikipedia-over-whatsapp/</guid>
      <description>&lt;h2 id=&#34;in-flight-messaging&#34;&gt;In-flight messaging&lt;/h2&gt;
&lt;p&gt;I was on a Delta flight the other day, and I was thinking about how they offer
free messaging from your smartphone.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;delta.png&#34; alt=&#34;Screenshot of Delta site that says &amp;amp;ldquo;Connect to Delta Wi-Fi to book, change, orcheck the status of your flight on delta.com, all without purchasing a Wi-Fipass. You can also message for free from your smartphone via iMessage, FacebookMessenger or WhatsApp (limited to text and emojis, SMSunavailable).&amp;amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;p&gt;How do they limit the wifi? Maybe they have some basic filter that says &amp;ldquo;deny
everything except the urls for these 3 messaging apps&amp;rdquo;, which makes sense
conceptually.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;delta-wifi.png&#34; alt=&#34;Basic diagram of my phone, Delta Wifi, and the rest of the internet, withDelta wifi blocking all types of requests other than messagingrequests&#34;&gt;&lt;/p&gt;
&lt;p&gt;What if we don&amp;rsquo;t want to pay for wifi and we still want to be able to browse the
web?&lt;/p&gt;
&lt;h2 id=&#34;tunneling-through-whatsapp&#34;&gt;Tunneling through WhatsApp&lt;/h2&gt;
&lt;p&gt;If the wifi is letting WhatsApp messages through, what if we used WhatsApp as a
vehicle for the information we really care about? Much like we encapsulate the
rest of our networking objects in higher-level objects, we could encapsulate
web pages inside of WhatsApp messages.&lt;/p&gt;
&lt;p&gt;We could start with a simple case, like Wikipedia. It&amp;rsquo;s mostly text-based, so
there should be no issue with sending images (which are blocked in the free
messaging over wifi). What if we built a basic service that allowed users to
query Wikipedia pages and read them over WhatsApp?&lt;/p&gt;
&lt;p&gt;Maybe it looks something like this, where a user sends a message to a WhatsApp
number. If we hook that number into a WhatsApp automated backend (probably via
Twilio), we can have that backend parse the message, query Wikipedia, and send
the response.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;system.png&#34; alt=&#34;System diagram of phone talking to WhatsApp that talks to a proxy service thattalks to Wikipedia&#34;&gt;&lt;/p&gt;
&lt;p&gt;There are two main components to this Wikipedia Over WhatsApp, or &lt;code&gt;WoW&lt;/code&gt; service:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The parsing of the messages and making requests to Wikipedia&lt;/li&gt;
&lt;li&gt;The WhatsApp interface for sending/receiving messages&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;requesting-articles-from-wikipedia&#34;&gt;Requesting articles from Wikipedia&lt;/h3&gt;
&lt;p&gt;Wikipedia has a &lt;a href=&#34;https://www.mediawiki.org/wiki/API:Main_page&#34;&gt;pretty approachable API&lt;/a&gt;
that allows you to do a few
things. We can keep our service very simple: let&amp;rsquo;s say the user has to send the
title of a Wikipedia article. If there&amp;rsquo;s an article that matches, we respond
with it. If there isn&amp;rsquo;t an article that matches, we can do a search for the
given text and return the top 10 articles.&lt;/p&gt;
&lt;p&gt;A very basic Python version might look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; urllib.parse
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; urllib.request
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# URL to query a page, format JSON, extract the text, and give us plain wiki text.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PAGE_WIKI_REQ_STRING &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://en.wikipedia.org/w/api.php?action=query&amp;amp;format=json&amp;amp;prop=extracts&amp;amp;list=&amp;amp;formatversion=2&amp;amp;exlimit=1&amp;amp;explaintext=1&amp;amp;exsectionformat=wiki&amp;amp;titles=&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# URL to search for articles for a search term.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SEARCH_WIKI_REQ_STRING &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://en.wikipedia.org/w/api.php?action=query&amp;amp;format=json&amp;amp;list=search&amp;amp;formatversion=2&amp;amp;srsearch=&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# This is a simple function for getting articles from wikipedia&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_wiki_page&lt;/span&gt;(page_name):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Encode the article name and build the URL.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    encoded_page_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; urllib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;quote(page_name&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;encode(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page_req_url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; PAGE_WIKI_REQ_STRING &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; encoded_page_name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    page_result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; urllib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;urlopen(page_req_url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result_body &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; page_result&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    result_json &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;loads(result_body&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# If it&amp;#39;s an article, we should have gotten an &amp;#39;extract&amp;#39;, so we can send it.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# In our case, this will be the entire article.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    query_result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; result_json[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;query&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pages&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;extract&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; query_result &lt;span style=&#34;color:#f92672&#34;&gt;and&lt;/span&gt; query_result[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;extract&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; query_result[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;extract&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# If we don&amp;#39;t have an article, we can instead search for articles for&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# that term and return the top 10 results.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        result_string &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;I didn&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;t find a perfect match for &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            page_name &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;, but you can try one of the following:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Try to search for it now.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        search_req_url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; SEARCH_WIKI_REQ_STRING &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; encoded_page_name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        search_result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; urllib&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;urlopen(search_req_url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        search_result_body &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; search_result&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        search_result_json &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;loads(search_result_body&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; result &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; search_result_json[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;query&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;search&amp;#39;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            result_string &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; result[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; result_string
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To test this, we could even throw it together for a quick CLI tool:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt; code &lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; above
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        page_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; page_name&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strip() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            page_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; input(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Please enter a Wikipedia page title.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        get_wiki_page(page_name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running that gives the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;cli.png&#34; alt=&#34;Screenshot of accessing Wikipedia via the CLI&#34;&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a good start! Given a page name, we can search for articles or get the
requested article. The formatting is wonky, but we&amp;rsquo;re just having fun here.&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&#34;the-whatsapp-interface&#34;&gt;The WhatsApp interface&lt;/h3&gt;
&lt;p&gt;Like a good engineer, I started with a Google search, and one of the first
results was exactly on the money:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.twilio.com/blog/receive-whatsapp-messages-python-flask-twilio&#34;&gt;How to Receive WhatsApp Messages in Python Using Flask and Twilio&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t bore you with the details, but the TL;DR is that it&amp;rsquo;s pretty easy to set
up a sandbox Twilio WhatsApp environment that you can send and receive messages
with. I hooked this up with a few changes, and the relevant parts are in the
following diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;mvp.png&#34; alt=&#34;Diagram of the various components connected from the user to the service andback&#34;&gt;&lt;/p&gt;
&lt;p&gt;The flow is roughly like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The user sends a message to the WhatsApp number that corresponds with a Twilio
number&lt;/li&gt;
&lt;li&gt;That Twilio number is connected to Twilio hooks that say &amp;ldquo;when I receive a
message, post it to this URL&amp;rdquo;&lt;/li&gt;
&lt;li&gt;That URL corresponds to an ngrok URL fronting my WoW service running on my
computer&lt;/li&gt;
&lt;li&gt;My WoW service receives the message and passes it to the &lt;code&gt;get_wiki_page&lt;/code&gt; code
above&lt;/li&gt;
&lt;li&gt;Once we have a response, we can send it back through Twilio to the user&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;The only issues ended up being that 1) WhatsApp messages are limited to 1600
characters, while Wikipedia pages are frequently much longer and 2) the basic
free accounts I was using rate-limit to ~1QPS, so if you try to chunk up a
message and send it as many messages, you may get throttled. To handle those, we
can do some basic chunking and delay between messages. You&amp;rsquo;re on a plane after
all; you&amp;rsquo;ve got nowhere to go!&lt;/p&gt;
&lt;p&gt;This simple &lt;code&gt;WoW&lt;/code&gt; service, with its message handling, ended up looking something like
this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; os
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; dotenv &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; load_dotenv
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; flask &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Flask, request
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; twilio.twiml.messaging_response &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; MessagingResponse
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; twilio.rest &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Client
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; wow
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Leave some room for the progress meter, like `(1/20) `&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MESSAGE_SIZE &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1590&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;load_dotenv()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;app &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Flask(__name__)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TWILIO_ACCOUNT_SID &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;environ&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;TWILIO_ACCOUNT_SID&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TWILIO_AUTH_TOKEN &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; os&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;environ&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;TWILIO_AUTH_TOKEN&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# https://www.twilio.com/blog/receive-whatsapp-messages-python-flask-twilio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;respond&lt;/span&gt;(request, full_message):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Break the message up into chunks that fall under the WhatsApp character&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# limit and send them with a delay in between.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    chunks &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (len(full_message) &lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; MESSAGE_SIZE) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(chunks):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; MESSAGE_SIZE
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        end &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; start &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; MESSAGE_SIZE
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        progress &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;) &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, chunks)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        message_chunk &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; progress &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; full_message[start:end]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;messages&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;create(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            body&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;message_chunk,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            from_&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;form&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;To&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            to&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;form&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;From&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; str(MessagingResponse())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;@app&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;route(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/message&amp;#39;&lt;/span&gt;, methods&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;reply&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    message &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;form&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Body&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; message:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; wow&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get_wiki_page(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; respond(request, result)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once I wired everything up and &lt;a href=&#34;debugging.png&#34;&gt;debugged&lt;/a&gt; a little, it ended up
working pretty smoothly!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;cheese.PNG&#34; alt=&#34;Screenshot of the basic bot working in WhatsApp&#34;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, I now have 20 full messages of the &lt;a href=&#34;https://en.wikipedia.org/wiki/Cheese&#34;&gt;entire Wikipedia page on Cheese&lt;/a&gt;
to read, and this will keep me plenty busy for the flight!&lt;/p&gt;
&lt;h3 id=&#34;what-would-this-need-to-go-past-a-prototype&#34;&gt;What would this need to go past a prototype?&lt;/h3&gt;
&lt;p&gt;For one, like most of my side projects, my artificial limit here was not paying
for anything, so I&amp;rsquo;m rate limited, using the sandbox, and running the service
off of my own computer.&lt;/p&gt;
&lt;p&gt;The user experience is also pretty rough - maybe we&amp;rsquo;d want to allow for an easy
way to get to the next page? Maybe paginating so you don&amp;rsquo;t get the full article
all at once?  Maybe sending a section at a time, so if you&amp;rsquo;re really just
interested in the history of the harmonica, you don&amp;rsquo;t have to get to the rest of
the page?&lt;/p&gt;
&lt;p&gt;Nothing quite kills the fun of a silly side project like having to do anything
real for it, and as soon as I started to read the guidelines for the WhatsApp
Business API and integrating with other Meta services, I realized I wasn&amp;rsquo;t going
to go through with setting this up as a publicly-available service. Feel free to
set your own up, though!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Back to the original question, though: would this really work? I think so off
the top of my head, but I&amp;rsquo;ll just have to try setting up a sandbox again before
my next flight.&lt;/p&gt;
&lt;p&gt;Either way, a fun little toy :)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A basic ear training note matching game</title>
      <link>https://alexanderell.is/posts/note-matching/</link>
      <pubDate>Sat, 14 May 2022 01:03:02 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/note-matching/</guid>
      <description>&lt;noscript&gt;
  This is a game for matching musical notes.
&lt;p&gt;JavaScript is needed to run this game. If you don&amp;rsquo;t have it enabled, I totally get it — I &lt;a
href=&#34;https://alexanderell.is/posts/taking-over-my-clipboard/&#34; target=&#34;blank_&#34;&gt;often&lt;/a&gt; &lt;a href=&#34;https://alexanderell.is/posts/attention-javascript&#34;
target=&#34;blank_&#34;&gt;don&amp;rsquo;t like&lt;/a&gt; it either.
&lt;/noscript&gt;&lt;/p&gt;
&lt;p&gt;This is a simple ear-training game for matching notes. Play along with your favorite instrument!&lt;/p&gt;
&lt;p&gt;You can also try singing/whistling, but note that &lt;em&gt;the pitch detection is &lt;a href=&#34;https://alexanderell.is/posts/tuner/&#34;&gt;hand rolled and rough around the edges&lt;/a&gt; — apologies if it makes any mistakes, especially for lower notes.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how to play:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Press the start button to begin&lt;/li&gt;
&lt;li&gt;Listen to the notes, then play them yourself&lt;/li&gt;
&lt;li&gt;(Optional): Hit Replay to listen to the notes again&lt;/li&gt;
&lt;li&gt;Play around with the options and continue as long as you want :)&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;&lt;b&gt;Options (they&amp;rsquo;ll apply to the next round):&lt;/b&gt;
&lt;br&gt;
&lt;label for=&#34;numNotes&#34;&gt;Number of notes:&lt;/label&gt;
&lt;select name=&#34;numNotes&#34; id=&#34;numNotes&#34;&gt;&lt;/p&gt;
  &lt;option value=&#34;1&#34;&gt;1&lt;/option&gt;
  &lt;option value=&#34;2&#34;&gt;2&lt;/option&gt;
  &lt;option value=&#34;3&#34;&gt;3&lt;/option&gt;
  &lt;option value=&#34;4&#34;&gt;4&lt;/option&gt;
&lt;/select&gt;
&lt;br&gt;
&lt;label for=&#34;numRepeats&#34;&gt;Number of repeated success required:&lt;/label&gt;
&lt;select name=&#34;numRepeats&#34; id=&#34;numRepeats&#34;&gt;
  &lt;option value=&#34;0&#34;&gt;0&lt;/option&gt;
  &lt;option value=&#34;1&#34;&gt;1&lt;/option&gt;
  &lt;option value=&#34;2&#34;&gt;2&lt;/option&gt;
&lt;/select&gt;
&lt;br&gt;
&lt;label for=&#34;minDuration&#34;&gt;Min note duration (the one you play):&lt;/label&gt;
&lt;select name=&#34;minDuration&#34; id=&#34;minDuration&#34;&gt;
  &lt;option value=&#34;100&#34;&gt;100ms&lt;/option&gt;
  &lt;option value=&#34;200&#34;&gt;200ms&lt;/option&gt;
  &lt;option value=&#34;300&#34; selected&gt;300ms&lt;/option&gt;
  &lt;option value=&#34;500&#34;&gt;500ms&lt;/option&gt;
&lt;/select&gt;
&lt;br&gt;
&lt;label for=&#34;show-staff&#34;&gt;Show notes&lt;/label&gt;
&lt;input type=&#34;checkbox&#34; id=&#34;show-staff&#34; name=&#34;show-staff&#34; value=&#34;true&#34; checked=&#34;true&#34;&gt;
&lt;br&gt;
&lt;div id=&#34;game&#34;&gt;
  &lt;div&gt;Total correct: &lt;span id=&#34;totalCorrect&#34;&gt;0&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;Perfect streak: &lt;span id=&#34;perfectStreak&#34;&gt;0&lt;/span&gt;&lt;/div&gt;
  &lt;div&gt;Best perfect streak: &lt;span id=&#34;bestPerfectStreak&#34;&gt;0&lt;/span&gt;&lt;/div&gt;
  &lt;button id=&#34;startButton&#34;&gt;Start&lt;/button&gt;
  &lt;br&gt;
  &lt;button id=&#34;replayButton&#34; disabled&gt;Replay&lt;/button&gt;
  &lt;br&gt;
  &lt;div id=&#34;staff&#34;&gt;&lt;/div&gt;
  &lt;br&gt;
  &lt;div id=&#34;status&#34;&gt;&lt;/div&gt;
  &lt;div id=&#34;results&#34;&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;script src=&#34;autocorrelate.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;note-matching.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;vexflow-debug.js&#34;&gt;&lt;/script&gt;
&lt;style&gt;
  .note {
    margin: 0 5px;
    min-width: 45px;
    display: inline-block;
    font-size: 26px;
  }
&lt;/style&gt;&lt;blockquote&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>&#34;I don&#39;t know the numbers&#34;: a math puzzle</title>
      <link>https://alexanderell.is/posts/numbers-game/</link>
      <pubDate>Sun, 08 May 2022 15:29:23 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/numbers-game/</guid>
      <description>&lt;p&gt;I saw a math puzzle the other day on &lt;a href=&#34;https://news.ycombinator.com/item?id=31293611&#34;&gt;Hacker News&lt;/a&gt;. It reads as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Two numbers are chosen randomly, both are positive integers smaller than 100. Sandy is told the sum of the numbers, while Peter is told the product of the numbers.&lt;/p&gt;
&lt;p&gt;Then, this dialog occurs between Sandy and Peter:&lt;/p&gt;
&lt;p&gt;Peter: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Sandy: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Peter: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Sandy: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Peter: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Sandy: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Peter: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Sandy: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Peter: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Sandy: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Peter: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Sandy: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Peter: I don&amp;rsquo;t know the numbers. &lt;br&gt;
Sandy: I don&amp;rsquo;t know the numbers. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;Peter: I do know the numbers.&lt;/p&gt;
&lt;p&gt;What are the numbers?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At first glance this looks impenetrable, but I&amp;rsquo;d encourage you to give it a think if you&amp;rsquo;d like to. I&amp;rsquo;ll be walking through exactly how it works and how to solve it (with the help of a computer).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Spoilers below, be warned&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Again, at first glance, it doesn&amp;rsquo;t look like any of this information would be helpful, but the trick is that each time they say they don&amp;rsquo;t know the answer, the other party is able to narrow down the options for what the numbers could be. If you do this enough times, there&amp;rsquo;s only one pair of numbers left, at which point Peter knows the answer.&lt;/p&gt;
&lt;p&gt;I found it helpful to think about this in a few different ways. First, let&amp;rsquo;s say we have a list of all of the pairs of numbers - we can think about those as our candidates. To simplify, let&amp;rsquo;s say that the order doesn&amp;rsquo;t matter (&lt;code&gt;(1, 2)&lt;/code&gt; is the same as &lt;code&gt;(2, 1)&lt;/code&gt;) and we just keep track of one of the duplicates.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;candidates: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s also imagine a big representation of all of the possible &lt;strong&gt;sums&lt;/strong&gt; and &lt;strong&gt;products&lt;/strong&gt; and the pairs of numbers that make up those sums and products. Let&amp;rsquo;s say they&amp;rsquo;re maps, where the keys are the sums/products and the values are lists of the pairs that make up those sums/products. Furthermore, let&amp;rsquo;s say that both Sandy and Peter have full knowledge of these maps.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sums: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)],  &lt;span style=&#34;color:#75715e&#34;&gt;# The only pair that adds up to 2 is (1, 1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],  &lt;span style=&#34;color:#75715e&#34;&gt;# For 4, we have two pairs: (1, 3) and (2, 2)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# And so on.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;198&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;products: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)],  &lt;span style=&#34;color:#75715e&#34;&gt;# The only pair that multiply together to make 1 is (1, 1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],  &lt;span style=&#34;color:#75715e&#34;&gt;# For 4, we have two pairs: (1, 4) and (2, 2)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# And so on.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;9801&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The trick is in those entries that are only made up of a single pair.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at Peter first. Peter knows the product of the two numbers. Peter can look up this product in the &lt;code&gt;products&lt;/code&gt; map - if there is only one pair that makes up that product, then those &lt;em&gt;must&lt;/em&gt; be the numbers! When Peter tells us that he doesn&amp;rsquo;t know the numbers, it means that the product &lt;em&gt;must not be any product that only has one pair&lt;/em&gt;. This means that every time Peter doesn&amp;rsquo;t know, we can remove each pair that is the only pair for a product, and those pairs can be removed from the list of potential candidates.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at those above lists and Peter&amp;rsquo;s first claim. At the start, we have a full list of candidate pairs, a full &lt;code&gt;sums&lt;/code&gt; map, and a full &lt;code&gt;products&lt;/code&gt; map. Because Peter knows the product, when he says &amp;ldquo;I don&amp;rsquo;t know the numbers&amp;rdquo;, it means that he looked at the possible pairs that made up this product and found multiple potential pairs - otherwise he would immediately know the numbers. This means that we can then disqualify products that are made up of one pair. &lt;code&gt;1&lt;/code&gt; as a product, for instance, is only made from multiplying the pair &lt;code&gt;(1, 1)&lt;/code&gt;. Since we know that Peter doesn&amp;rsquo;t immediately know it as the solution, it means that it can&amp;rsquo;t be &lt;code&gt;(1, 1)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Similarly, if the product was &lt;code&gt;9801&lt;/code&gt;, Peter would have immediately known the pair was &lt;code&gt;(99, 99)&lt;/code&gt;. Because he can&amp;rsquo;t yet say for certain, it means that the numbers must not be &lt;code&gt;(99, 99)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re able to now remove all candidates that were the only factors that made up a product. For the first round, this is notably &lt;code&gt;1&lt;/code&gt; times each prime and each prime multiplied by another prime that results in a number greater than 100. Importantly, we can also remove them and their products/sums from the &lt;code&gt;products&lt;/code&gt; map &lt;em&gt;and&lt;/em&gt; the &lt;code&gt;sums&lt;/code&gt; map.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# After culling the candidates from the first round&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;candidates: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sums: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;:&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;[&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;:&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;[&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;, (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;: [(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;, (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# And so on.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;198&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;products: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;:&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;[&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;:&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;[&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;:&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;[&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# And so on.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;:&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;[&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;(&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;,&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;)&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;]&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;̶&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Note: the above uses strikethrough code - if it looks funny on your screen, I apologize!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# In that case, the culling is an exercise left to the reader.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the trick. Because the space of possible candidates has been reduced, it means the possible candidates that make up each sum and product have also been reduced. Let&amp;rsquo;s look at the updated &lt;code&gt;candidates&lt;/code&gt;, &lt;code&gt;sums&lt;/code&gt;, and &lt;code&gt;products&lt;/code&gt;, filling in a few more previously omitted values:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;candidates: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;88&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sums: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# And so on.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;178&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;88&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;179&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;products: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],  &lt;span style=&#34;color:#75715e&#34;&gt;# For 4, we have two pairs: (1, 4) and (2, 2)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# And so on.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;7920&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;88&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;90&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;99&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, it&amp;rsquo;s Sandy&amp;rsquo;s turn to say that she doesn&amp;rsquo;t know the numbers. Because Sandy knows the &lt;strong&gt;sum&lt;/strong&gt;, again it must mean that the sum can&amp;rsquo;t be any sum with only one pair as an option in &lt;code&gt;sums&lt;/code&gt;, so we can again cull those pairs. In the updated &lt;code&gt;sums&lt;/code&gt; structure, we can see that 4 is now only made up of a single candidate pair, &lt;code&gt;(2, 2)&lt;/code&gt;. If the sum was 4, Sandy would know immediately what the pair was, but since she doesn&amp;rsquo;t, it means the pair cannot be &lt;code&gt;(2, 2)&lt;/code&gt;, and we can remove it from the candidates.&lt;/p&gt;
&lt;p&gt;Every round, we&amp;rsquo;re disqualifying potential candidates. Because we&amp;rsquo;re also able to remove them from the &lt;code&gt;sums&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt; as potential options, it means that each protagonist is working on a new collection every time, giving a little more information every time they say they don&amp;rsquo;t know the number.&lt;/p&gt;
&lt;p&gt;This pattern will continue, with a certain number of options being removed every time. At some point, Peter finally says that he does know the numbers - finally, there is only one product left with one potential pair, and that is the answer. Importantly, it has to go down to a single product - otherwise there would be multiple answers! That&amp;rsquo;s why this version has Peter claiming to know after 14 rounds, because it limits the answer to a single pair (although there are other options - see later).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This would be a little rough to do by hand, but luckily we can have the computer do it for us.&lt;/p&gt;
&lt;br&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;N &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Helper functions to regenerate the sums/products data structures every round.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Not optimal but hey this is for fun.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;generate_sums&lt;/span&gt;(candidate_pairs):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sums &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; tuple_pair &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; candidate_pairs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (i, j) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tuple_pair
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sum &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; j
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; sum &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; sums:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sums[sum] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [tuple_pair]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sums[sum]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(tuple_pair)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; sums
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;generate_products&lt;/span&gt;(candidate_pairs):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    products &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; tuple_pair &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; candidate_pairs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (i, j) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tuple_pair
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        product &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; j
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; product &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; products:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            products[product] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [tuple_pair]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            products[product]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;append(tuple_pair)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; products
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;find_pairs&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# First, generate all possible pairs from (1, 1) to (N-1, N-1).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    candidate_pairs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; set()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, N):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(i, N):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            candidate_pairs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add((i, j))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Swap between hearing from Peter or Sandy every round (product or sum)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    do_not_know_product &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    round &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sums &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; generate_sums(candidate_pairs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        products &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; generate_products(candidate_pairs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# First, check if there&amp;#39;s a single product left with one pair.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; round &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; product &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; products:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(products[product]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Peter: I do know the numbers&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    print(products[product][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; do_not_know_product:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Peter: I don&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;t know the numbers&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Go through products. For any product that only has a single pair,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# we can remove those from the candidates for the next round.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; product &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; products:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(products[product]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    tuple_pair &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; products[product][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;# Remove from candidates.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    candidate_pairs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(tuple_pair)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            do_not_know_product &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Sandy: I don&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;t know the numbers&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Go through sums. For any product that only has a single pair,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# we can remove those from the candidates for the next round.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; sum &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; sums:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(sums[sum]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    tuple_pair &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sums[sum][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;# Remove from candidates.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    candidate_pairs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(tuple_pair)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            do_not_know_product &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        round &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    find_pairs()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running that leads to the following, just like the puzzle, though now it includes an answer:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;
Peter: I don&amp;#39;t know the numbers
Sandy: I don&amp;#39;t know the numbers
Peter: I don&amp;#39;t know the numbers
Sandy: I don&amp;#39;t know the numbers
Peter: I don&amp;#39;t know the numbers
Sandy: I don&amp;#39;t know the numbers
Peter: I don&amp;#39;t know the numbers
Sandy: I don&amp;#39;t know the numbers
Peter: I don&amp;#39;t know the numbers
Sandy: I don&amp;#39;t know the numbers
Peter: I don&amp;#39;t know the numbers
Sandy: I don&amp;#39;t know the numbers
Peter: I don&amp;#39;t know the numbers
Sandy: I don&amp;#39;t know the numbers
Peter: I do know the numbers
(77, 84)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There&amp;rsquo;s the pair! 77 and 84.&lt;/p&gt;
&lt;p&gt;Interestingly, as part of debugging this solution, I found that there could be different answers at different numbers of rounds. As long as there is a single product made up of a single pair, that could be the answer. We can update the &lt;code&gt;find_pairs&lt;/code&gt; function to check for potential solutions at various round lengths (and stop before we go to infinity):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;find_pairs&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# First, generate all possible pairs from (1, 1) to (N-1, N-1).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    candidate_pairs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; set()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, N):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(i, N):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            candidate_pairs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add((i, j))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Swap between hearing from Peter or Sandy every round (product or sum)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    do_not_know_product &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    round &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sums &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; generate_sums(candidate_pairs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        products &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; generate_products(candidate_pairs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Peter:&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; do_not_know_product &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Sandy:&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        potential_final &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# First, check if there&amp;#39;s a single product left with one pair.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; do_not_know_product:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            count_unique_product_pairs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; product &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; products:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(products[product]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    count_unique_product_pairs &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    potential_final &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; products[product][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; count_unique_product_pairs &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(name, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;I could know the numbers after&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      round, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;rounds:&amp;#39;&lt;/span&gt;, potential_final)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            count_unique_sum_pairs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; sum &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; sums:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(sums[sum]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    count_unique_sum_pairs &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    potential_final &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sums[sum][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; count_unique_sum_pairs &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                print(name, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;I could know the numbers after&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      round, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;rounds:&amp;#39;&lt;/span&gt;, potential_final)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        num_candidates_before_cull &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; len(candidate_pairs)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; do_not_know_product:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Go through products. For any product that only has a single pair,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# we can remove those from the candidates for the next round.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; product &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; products:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(products[product]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    tuple_pair &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; products[product][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;# Remove from candidates.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    candidate_pairs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(tuple_pair)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            do_not_know_product &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# Go through sums. For any product that only has a single pair,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;# we can remove those from the candidates for the next round.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; sum &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; sums:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(sums[sum]) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    tuple_pair &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sums[sum][&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;# Remove from candidates.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    candidate_pairs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;remove(tuple_pair)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            do_not_know_product &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        round &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# If we didn&amp;#39;t remove any pairs, we may be in an infinite loop and want&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# to stop.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(candidate_pairs) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; num_candidates_before_cull:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using this version, we now get the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;
Sandy: I could know the numbers after 6 rounds: (72, 99)
Peter: I could know the numbers after 7 rounds: (81, 88)
Sandy: I could know the numbers after 8 rounds: (70, 99)
Peter: I could know the numbers after 9 rounds: (77, 90)
Sandy: I could know the numbers after 10 rounds: (72, 95)
Peter: I could know the numbers after 11 rounds: (76, 90)
Sandy: I could know the numbers after 12 rounds: (70, 96)
Peter: I could know the numbers after 13 rounds: (80, 84)
Sandy: I could know the numbers after 14 rounds: (66, 98)
Peter: I could know the numbers after 15 rounds: (77, 84)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This means that the puzzle could be a number of different round combinations, with either Sandy or Peter saying they know the answer! In my opinion, showing 15 &amp;ldquo;I don&amp;rsquo;t know&amp;quot;s makes the puzzle all the more confuddling, which adds to the puzzleness.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;How many rounds does it go? Here, we see that it can progress until there are no culled candidates - for &lt;code&gt;N = 100&lt;/code&gt;, we go 15 rounds before we stop. What does that look like for a different value of N? We can explore that easily, and I think there&amp;rsquo;s a fun question here: what&amp;rsquo;s the lowest value of N that has an answer?&lt;/p&gt;
&lt;p&gt;For the lowest, we can just do the following, adding &lt;code&gt;n&lt;/code&gt; as an argument:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;find_pairs&lt;/span&gt;(n):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# First, generate all possible pairs from (1, 1) to (N-1, N-1).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    candidate_pairs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; set()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, n):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(i, n):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            candidate_pairs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add((i, j))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# ... everything else the same&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; range(&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;N =&amp;#39;&lt;/span&gt;, i)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        find_pairs(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;*****&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;N = 0
*****
N = 1
*****
N = 2
Peter: I could know the numbers after 1 rounds: (1, 1)
*****
N = 3
*****
N = 4
*****
N = 5
*****
N = 6
*****
N = 7
*****
N = 8
*****
N = 9
*****
N = 10
Peter: I could know the numbers after 3 rounds: (1, 4)
Sandy: I could know the numbers after 4 rounds: (2, 3)
Peter: I could know the numbers after 5 rounds: (1, 6)
Sandy: I could know the numbers after 6 rounds: (3, 4)
Peter: I could know the numbers after 7 rounds: (2, 6)
Sandy: I could know the numbers after 8 rounds: (4, 4)
Peter: I could know the numbers after 9 rounds: (2, 8)
*****
... a lot more
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It looks like the lowest is &lt;code&gt;N = 2&lt;/code&gt;, which is not a lot of fun, because the only pair is &lt;code&gt;(1, 1)&lt;/code&gt;. Not a very interesting puzzle! The next one is &lt;code&gt;N = 10&lt;/code&gt; for values 1-9, which can be done in 3-9 turns. That&amp;rsquo;s pretty interesting, since we could formulate the riddle differently, though the large number of numbers and rounds makes it all the more befuddling.&lt;/p&gt;
&lt;p&gt;I think the other interesting thing here is that for some values of N, there is no solution! Let&amp;rsquo;s look at &lt;code&gt;N = 4&lt;/code&gt;, where the numbers are 1-3. These tables are pretty easy to write out by hand:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;candidates: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sums: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;), (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;products: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;: [(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;OK shoot, if we remove all of the products that are made up of a single pair, we don&amp;rsquo;t have any pairs left, which makes sense. Because not all values of N lead to a solution, this also means that trying to reason through the problem for low values of N is extra tricky, since it doesn&amp;rsquo;t actually lead you to a solution!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Overall, a fun puzzle. Here are a few closing extra credit questions for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does it matter whether Peter or Sandy talks first?&lt;/li&gt;
&lt;li&gt;What would happen if we had a third person that knew the absolute value of the numbers subtracted from each other? Would the order of the three matter?&lt;/li&gt;
&lt;li&gt;What if that third person knew the numbers subtracted, where the order of the numbers mattered?&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Getting comfortable with being uncomfortable</title>
      <link>https://alexanderell.is/posts/uncomfortable/</link>
      <pubDate>Sun, 01 May 2022 00:28:44 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/uncomfortable/</guid>
      <description>&lt;p&gt;Software engineering is cursed with uncertainty.&lt;/p&gt;
&lt;p&gt;When we&amp;rsquo;re solving big new problems with software, if the exact problem has been solved before, we could just reuse the solution that already exists. Frequently though, the exact problem (or combination of problems) hasn&amp;rsquo;t been solved before, and we need to come up with a way to solve it.&lt;/p&gt;
&lt;p&gt;Have you felt that feeling? That moment of uncertainty, where you don&amp;rsquo;t know what the solution will look like. You&amp;rsquo;ve solved many other problems before this one (and so far they keep paying you), but now you have to reach into the creative ether again to come up with a way to solve this new one.&lt;/p&gt;
&lt;p&gt;(As an aside, one of my pet theories is that this is part of the reason behind all of our imposter syndromes, since every new problem is a new chance to face this uncertainty. &lt;em&gt;What if I can&amp;rsquo;t solve this new problem? Is this going to be the one where they finally figure out I&amp;rsquo;m a fraud?&lt;/em&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;It can be uncomfortable. It&amp;rsquo;s like diving into a new codebase, where at first it&amp;rsquo;s all a series of files and unfamiliar names. It&amp;rsquo;s like learning a new technology, where you&amp;rsquo;re trying to map these new concepts to the languages you&amp;rsquo;ve seen before while also seeing what&amp;rsquo;s new. It&amp;rsquo;s these moments of uncertainty, where everything is still an amorphous, blurry mist that has yet to come fully into focus.&lt;/p&gt;
&lt;p&gt;Slowly, over time, the mist starts to clear up. Suddenly you can see how things are connected. After a chat with a coworker, a deep-dive into the code, or a &lt;a href=&#34;https://alexanderell.is/posts/trust-in-your-unconscious/&#34;&gt;walk around the block&lt;/a&gt;, something clicks, and some of the pieces start falling into place. The mist transforms into an outline, the outline into a conversation, the conversation into a diagram, the diagram into a few pull requests, the pull requests into follow up pull requests, and finally this vague problem has been translated from that amorphous blob into concrete lines of code.&lt;/p&gt;
&lt;p&gt;This process is one of the most interesting parts of software development. I&amp;rsquo;m lucky to be able to work on interesting problems and spend a lot of time learning, and I&amp;rsquo;ve spent a lot of my time in this uncomfortable zone. I&amp;rsquo;ve gotten used to it and, dare I say, almost started to enjoy it. There&amp;rsquo;s still that moment of uncertainty, but I have my ways to get through it. I think it&amp;rsquo;s an important skill to build, this familiarity with the unknown and the uncertainty, and finding ways that are effective for you to get through it can be very empowering&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve made it through every other problem so far, and chances are good we can make it through this next one, too.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I particularly enjoy &lt;a href=&#34;https://news.ycombinator.com/item?id=30901046&#34;&gt;Walter Bright&amp;rsquo;s method&lt;/a&gt;: &amp;ldquo;&lt;em&gt;1. load my brain with all the context of the problem 2. go out for a run, which bounces it all around in my brain until things fall into place 3. write the solution when I get back&lt;/em&gt;&amp;rdquo;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Opinionated variable names</title>
      <link>https://alexanderell.is/posts/opinionated-variable-names/</link>
      <pubDate>Sun, 17 Apr 2022 13:08:57 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/opinionated-variable-names/</guid>
      <description>&lt;p&gt;I think a lot about the human part of code, especially the work our brains do when writing and reading it and interfacing with the computer. Rachelbythebay had a great take on a way to inject some brain-processing into a technical task - when you&amp;rsquo;re running a command to affect &lt;code&gt;n&lt;/code&gt; servers, maybe it would be good to type that number in manually.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The idea is to force you to take in that number through your usual input devices (I&amp;rsquo;d say eyes, but some people are using text-to-speech stuff or similar, and they count too), chew on it with your wetware, and then feed it back into the computer somehow. Adding a few extra steps like this will hopefully activate enough of your brain to make you stop short before blowing off your entire leg with a giant foot-gun.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rachelbythebay.com/w/2020/10/26/num/&#34;&gt;Rachelbythebay: &lt;em&gt;Type in the exact number of machines to proceed&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I like this idea about a mental check, even if it&amp;rsquo;s just a quick second of consideration, and it&amp;rsquo;s interesting to see how it can show up in naming, as well.&lt;/p&gt;
&lt;p&gt;Take &lt;code&gt;dangerouslySetInnerHtml&lt;/code&gt;, for instance. It&amp;rsquo;s a function in React that, as the name implies, sets the inner HTML of an HTML element on the page. As the name also implies, this is generally scary and discouraged because of how easy it makes it to accidentally surface attack vectors for cross-site scripting. By prepending that adverb and explicitly calling it out as a potential footgun, it (hopefully) adds a layer of self-reflection when typing it in to use it, and it helps to raise a red flag that&amp;rsquo;s easy to pick up on when reviewing changes.&lt;/p&gt;
&lt;p&gt;It doesn&amp;rsquo;t all have to be so serious, though. One of Google&amp;rsquo;s helpful frameworks for building websites is named after a character from the Wizard of Oz universe. When running your local development server, when the server was ready, it would print an ASCII drawing of that character to your terminal. It even scaled the drawing to your terminal size, though the drawing was a little more abstract at smaller sizes. You could disable the drawing if you wanted to though, using an aptly named flag that was something like &lt;code&gt;--my_terminal_space_is_more_important_than_your_artistic_expression&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Petty? Definitely. Whimsical and lighthearted? Absolutely.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s fun to think about how these names we type out can impart meaning and context beyond the usual &amp;ldquo;what it is&amp;rdquo; or &amp;ldquo;what it does&amp;rdquo; naming. Like most things, a healthy serving of moderation is key. I don&amp;rsquo;t want to read every opinion in every function (nor would anyone want to see mine), but it&amp;rsquo;s fun to see from time to time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>What is a minute, really?</title>
      <link>https://alexanderell.is/posts/what-is-a-minute/</link>
      <pubDate>Sun, 10 Apr 2022 11:44:07 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/what-is-a-minute/</guid>
      <description>&lt;p&gt;What&amp;rsquo;s in a minute? It can be sixty long seconds, or it can be just a blink of the eye.&lt;/p&gt;
&lt;h3 id=&#34;easy-ways-to-slow-time-down&#34;&gt;Easy ways to slow time down&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re trying to slow time down, see how long a minute feels for any of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you&amp;rsquo;re able to, hold a plank for 60 seconds&lt;/li&gt;
&lt;li&gt;Read the &lt;a href=&#34;https://www.gutenberg.org/files/37134/37134-h/37134-h.htm#Page_7&#34;&gt;&lt;em&gt;Elementary Rules of Usage&lt;/em&gt; section from &lt;em&gt;The Elements of Style&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;If you&amp;rsquo;re able to, sprint at full speed for 60 seconds&lt;/li&gt;
&lt;li&gt;After asking a question to a group and hearing no response, wait for 60 seconds before offering the answer&lt;/li&gt;
&lt;li&gt;Try to hold your breath for 60 seconds&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;easy-ways-to-speed-time-up&#34;&gt;Easy ways to speed time up&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re trying to speed time up, see how long a few minutes feels for any of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go on TikTok (or your &lt;a href=&#34;https://alexanderell.is/posts/infinite-scroll/&#34;&gt;time-waster of choice&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Take a nap&lt;/li&gt;
&lt;li&gt;Get immersed in a good book&lt;/li&gt;
&lt;li&gt;Try to complete a 20-part multiple choice quiz in 10 minutes&lt;/li&gt;
&lt;li&gt;Be stuck in traffic while late for a flight&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;It&amp;rsquo;s funny how our perception of time can vary so greatly.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The tech interview prep industry</title>
      <link>https://alexanderell.is/posts/tech-interview-prep-industry/</link>
      <pubDate>Sun, 03 Apr 2022 17:55:05 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/tech-interview-prep-industry/</guid>
      <description>&lt;p&gt;Before I got into tech, I worked at a tutoring company in the Northern Virginia suburbs of Washington, D.C., doing a mix of schoolwork-related tutoring and standardized test prep.&lt;/p&gt;
&lt;p&gt;From a business perspective, it made so much sense - you have kids taking standardized tests and parents who wanted their kids to do well on them. Here we have the SAT, a measurable evaluation that can be an immediate help to your future if you do well on it (though whether or not it&amp;rsquo;s truly a good evaluation is well beyond the scope of this essay). For better or for worse, it makes up a non-trivial part of the criteria used to evaluate college applications.&lt;/p&gt;
&lt;p&gt;With that in mind, let&amp;rsquo;s play economist: if your SAT score allowed you better future prospects, how much would it be worth it to raise it by 100 points? Sure, we&amp;rsquo;d have to quantify those future prospects and the probabilities, but it generally seems like it would be a good thing. Furthermore, if you could pay someone to help your kid raise their score by 100 points, how much would you pay?&lt;/p&gt;
&lt;p&gt;Even if there isn&amp;rsquo;t a direct value we can calculate for future benefit on SAT score increase, it seems like one of those things that &lt;em&gt;should&lt;/em&gt; be true, at least. Talk about easy marketing!  From a business perspective, this seems like the perfect fit; for every graduating student, there&amp;rsquo;s a new student who will be taking these tests soon enough, frequently with parents that are more than happy to pay for help along the way.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a funny pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A standardized test is developed&lt;/li&gt;
&lt;li&gt;Doing well on that test is beneficial&lt;/li&gt;
&lt;li&gt;People are willing to pay for help to do better, especially if it&amp;rsquo;s for their kids&lt;/li&gt;
&lt;li&gt;Businesses pop up to fill that need&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;p&gt;You can see many examples of this pattern in many standardized tests in the US: the SAT, ACT, PSAT, GRE, MCAT, LSAT&amp;hellip;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;You see this with tech interviews as well.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know who first came up with this idea of having a candidate work through a tricky algorithm problem as part of their interview, but it has created a beast. Like a butterfly flapping its wings across the world to create a hurricane, we&amp;rsquo;re now in the depths of the algorithm interviews.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;(Note: whether or not these kinds of interviews are good is also beyond the scope of this essay)&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s really an odd game, these interviews. The rules are mostly there for you to see before you play, with books detailing the process, online sites dedicated to the questions asked, and many avenues to practice. Interestingly, some companies also tell you about the rules and what to expect, even going as far as recommending specific subject areas to study (or, helpfully, that they won&amp;rsquo;t be asking any dynamic programming questions). Some of the companies even recommend specific sites to practice on.&lt;/p&gt;
&lt;p&gt;Where did these sites come from? I think it&amp;rsquo;s the same pattern as before:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Companies started doing algorithm interviews&lt;/li&gt;
&lt;li&gt;Doing well on those interviews can be very beneficial&lt;/li&gt;
&lt;li&gt;People are willing to pay for ways to do better&lt;/li&gt;
&lt;li&gt;Businesses pop up to fill that need&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;p&gt;The similarities to the test prep industry are very interesting. Is an algorithm interview reflective of how an engineer works in their day-to-day job? Along those lines, what is the SAT really measuring? There&amp;rsquo;s this odd gap of artificiality between the test and the actual thing it&amp;rsquo;s purportedly testing for. Much like how bootcamps have popped up to fill in some of the gap between studying CS in college and the skills you need to successfully create websites, these businesses have popped up to help you bridge this gap between what you have to do as an engineer (or student) and what you have to do in the interview (or standardized test).&lt;/p&gt;
&lt;p&gt;Look at timing - even a little strategy about how to tackle questions in each SAT section can help students do better. Maybe you aren&amp;rsquo;t used to writing essays in 50 minutes, but that&amp;rsquo;s OK! With a little practice and timing (be sure to wear a watch), you can get more used to it. Is that really reflective of life after the test? Maybe you&amp;rsquo;ll get a multiple choice test in college you have to hurry for or have to write manually during an exam, but the hard part is all of the other learning you&amp;rsquo;ll have to do.&lt;/p&gt;
&lt;p&gt;For interviews, you can practice the timing, too.  These businesses can help you get used to solving problems quickly as you identify general patterns. Working as an engineer, will your bugs ever be timeboxed to a time limit? Maybe you have to hurry a little bit every now and then, but I certainly hope it isn&amp;rsquo;t always the case that you&amp;rsquo;re working under 45-minute time limits, writing everything from scratch.&lt;/p&gt;
&lt;p&gt;The other compelling similarity is that doing well after passing the test is not necessarily related to your performance on the test itself. When my wife was studying for the LSAT (the Law School Admission Test in the US), she found a tutor who had done tremendously well on the LSAT and pivoted that into a job helping other people study for the LSAT. I found this so interesting, because the mismatch between the test and working as a lawyer is just as striking (not to mention the mismatch between law school and working as a lawyer, but that&amp;rsquo;s also outside the scope here). Here was a test for preparedness to do well in law school (mostly in name, of course), but it clearly doesn&amp;rsquo;t take a lawyer to teach it.&lt;/p&gt;
&lt;p&gt;With the market for helping others pass interviews, suddenly if you&amp;rsquo;re a few years into your career, there&amp;rsquo;s another avenue to build a brand doing interview prep. Just look up &amp;ldquo;coding interview tips&amp;rdquo; on YouTube - if you&amp;rsquo;re pretty good at interviews, you can leverage that into building a personal brand, and you get bonus points if you have some of the big companies already on your resume.&lt;/p&gt;
&lt;p&gt;As a quick aside, I just wanted to note that there&amp;rsquo;s no disrespect meant towards those who are making money off of this; I&amp;rsquo;m more interested in this in a cultural way.&lt;/p&gt;
&lt;p&gt;There are also a few interesting differences between this interview/business cycle and the standardized tests above. First, financially, the math is much clearer in favor of spending now to get a positive result later, as doing very well on these interviews can have a direct mapping to future benefits. If you&amp;rsquo;re able to land a job that increases your salary by $50,000, suddenly the $40 you spend on a book and the $35 you spend on an online subscription pale in comparison to the upside.&lt;/p&gt;
&lt;p&gt;Additionally, there&amp;rsquo;s a strong self-fulfilling cycle here. Let&amp;rsquo;s say Company A does algorithm interviews and they know that there&amp;rsquo;s a gap between an engineer&amp;rsquo;s day-to-day responsibilities (such as writing good code to solve real business needs) and what you have to do in these interviews, so maybe they recommend a few test-prep sites to the candidate so they can get a little practice in. This would be like if signing up for the SAT came with a recommendation for a Kaplan study book - free marketing!&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;what-are-we-really-measuring-here&#34;&gt;What are we really measuring here?&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re measuring how someone communicates and thinks through a problem, what do you do when there&amp;rsquo;s a book that walks them through how companies want you to communicate and think through a problem in an interview?&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re trying to see how someone thinks about building complicated systems, what do you do when there are YouTube videos that walk them through how companies want them to work through building a complicated system?&lt;/p&gt;
&lt;p&gt;If your company has to recommend practicing in advance of the interview, is that indicative that the interview could be better? What about when there&amp;rsquo;s an entire industry dedicated to being better at technical interviews?&lt;/p&gt;
&lt;p&gt;What a weird balance.&lt;/p&gt;
&lt;p&gt;It makes me simultaneously want to do all of the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start an interview prep startup/personal brand and make money off of it, because shovels sell well in a gold rush and we might as well make a buck. Ideally we get some of these big companies to recommend our product, because that&amp;rsquo;s great marketing.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Swear off technical interviews, throw away all of my computers, and live in the woods without electricity.&lt;/li&gt;
&lt;li&gt;Play the dang game and do the stupid practice problems, because the upside is huge and real estate is expensive.&lt;/li&gt;
&lt;li&gt;Work hard to find better and more equitable ways of interviewing candidates in a kind and human-focused way.&lt;/li&gt;
&lt;/ol&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;I definitely don&amp;rsquo;t have all the answers. I think I&amp;rsquo;ll just focus on #4.&lt;/p&gt;
&lt;br&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Luckily, some companies are moving away from the usual format, with new debugging and pair-programming interviews. Many companies are still in deep.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;New startup idea: sell ad space in recruiter prep emails to interview prep companies. Extra points if we dogfood it and do that for the startup&amp;rsquo;s recruiting emails.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Now seeking investors for the world&amp;rsquo;s worst interviewing site - since we&amp;rsquo;re in a race to the bottom, let&amp;rsquo;s be the first to define that lower limit!&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Keeping a debugging lab notebook</title>
      <link>https://alexanderell.is/posts/debugging-lab-notebook/</link>
      <pubDate>Sun, 27 Mar 2022 13:09:00 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/debugging-lab-notebook/</guid>
      <description>&lt;p&gt;I like to keep a debugging lab notebook when I&amp;rsquo;m working through a particularly tricky problem.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s nothing fancy, just a new tab visit to &lt;a href=&#34;https://docs.new&#34;&gt;&lt;code&gt;docs.new&lt;/code&gt;&lt;/a&gt; and a quick &lt;code&gt;@today&lt;/code&gt; to add today&amp;rsquo;s date. I usually try to describe the problem and the specific issue I&amp;rsquo;m having, including any reproduction steps or screenshots (frequently copied over from the issue tracker). I try to summarize the issue and my understanding of how things work, though this frequently leads to a handful of open questions for me to go through to really understand what&amp;rsquo;s happening.&lt;/p&gt;
&lt;p&gt;What usually follows is a freeform jumble of writing down hypotheses, results, error messages, screenshots, explanations, and log statements (something like &lt;code&gt;print(&#39;alex here 7.5&#39;)&lt;/code&gt; is a classic, since if you add another log statement between existing ones, you can add another decimal place to show you&amp;rsquo;re in between). I try to err on the side of including too much information so that I can shift everything out of my immediate working memory onto something a little more permanent, giving a clear history of what I&amp;rsquo;ve attempted as I work through the problem, but also importantly, what open questions remain and what things I should attempt next.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This helps me in two main ways. First, it encourages me to take a minute to explain in writing what I&amp;rsquo;m seeing — if I can&amp;rsquo;t explain it, I probably don&amp;rsquo;t fully understand it. Much like rubber duck debugging, this makes me think it through and work through what&amp;rsquo;s happening, which is frequently enough to help me realize an important insight.&lt;/p&gt;
&lt;p&gt;The second main benefit ties into this as well, as the process of working through and writing things down encourages me to be more deliberate about the debugging path I take. It can be so tempting to just make a quick edit as a shot in the dark and rerun the test, and I&amp;rsquo;ve definitely done this plenty of times before. I find that I&amp;rsquo;m much more effective when I&amp;rsquo;m more deliberate about things, taking the time to explore each result and the next potential steps before moving forward.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s like the joke about the best way a junior engineer can level up: actually reading the error messages!&lt;/p&gt;
&lt;p&gt;Many of these benefits show up as well when you&amp;rsquo;re pair programming or even just explaining the issue to your rubber duck (or curious pet). The other thing I like about this written approach is that it&amp;rsquo;s saved for you. When you come back to it later, you can retrace your steps, copy the same saved output or rerun a test, and page the context back into your working memory. My brain works much better as a processing machine than as a storage device, and the more I can keep written down and off of my busy brain&amp;rsquo;s responsibilities, the better.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;At some point, finally, the problem clicks, and you realize the key insight that helps you solve it. You write up a quick summary, update the issue, and throw a quick explanation into the PR. The working notebook, full of roundabout exploration and steps taken down many false paths, has done its job. Occasionally I&amp;rsquo;ll look up how I solved a similar problem before or ran a test in a certain way, but generally, I rarely ever come back to them. For these debugging logs, that&amp;rsquo;s ok; it&amp;rsquo;s much more about how they&amp;rsquo;ve helped with the journey through.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Detecting pitch with the Web Audio API and autocorrelation</title>
      <link>https://alexanderell.is/posts/tuner/</link>
      <pubDate>Sun, 20 Mar 2022 13:00:15 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/tuner/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been playing with the Web Audio API recently, and I made a basic app that will detect the pitch of incoming tones using your microphone. You can try it by pressing &amp;ldquo;Start&amp;rdquo; (be sure to try both the sine wave and the frequency displays), and I&amp;rsquo;ll be walking through how it works below.&lt;/p&gt;
&lt;p&gt;&lt;canvas class=&#34;visualizer&#34; width=&#34;640&#34; height=&#34;100&#34;&gt;&lt;/canvas&gt;
&lt;br&gt;
&lt;button id=&#34;init&#34; onClick=&#34;init()&#34;&gt;Start&lt;/button&gt;&lt;/p&gt;
&lt;h1 id=&#34;note&#34;&gt;Press start to begin&lt;/h1&gt;
&lt;legend&gt;Rounding options:&lt;/legend&gt;
&lt;div&gt;
  &lt;input type=&#34;radio&#34; id=&#34;roundingNone&#34; name=&#34;rounding&#34; value=&#34;none&#34; &gt;
  &lt;label for=&#34;roundingNone&#34;&gt;No rounding&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;roundingMedium&#34; name=&#34;rounding&#34; value=&#34;hz&#34; checked&gt;
  &lt;label for=&#34;roundingMedium&#34;&gt;Round to nearest Hz&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;roundingHard&#34; name=&#34;rounding&#34; value=&#34;note&#34;&gt;
  &lt;label for=&#34;roundingHard&#34;&gt;Round to nearest note&lt;/label&gt;
  &lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;legend&gt;Smoothing options:&lt;/legend&gt;
&lt;div&gt;
  &lt;input type=&#34;radio&#34; id=&#34;smoothingNone&#34; name=&#34;smoothing&#34; value=&#34;none&#34;&gt;
  &lt;label for=&#34;smoothingNone&#34;&gt;No smoothing&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;smoothingMedium&#34; name=&#34;smoothing&#34; value=&#34;basic&#34; checked&gt;
  &lt;label for=&#34;smoothingMedium&#34;&gt;Basic smoothing&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;smoothingHard&#34; name=&#34;smoothing&#34; value=&#34;very&#34;&gt;
  &lt;label for=&#34;smoothingHard&#34;&gt;Very smoothed (note must be more consistent and held for longer)&lt;/label&gt;
  &lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;legend&gt;Display (click Start after you switch):&lt;/legend&gt;
&lt;div&gt;
  &lt;input type=&#34;radio&#34; id=&#34;displaySine&#34; name=&#34;display&#34; value=&#34;sine&#34; checked&gt;
  &lt;label for=&#34;displaySine&#34;&gt;Sine wave&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;displayFrequency&#34; name=&#34;display&#34; value=&#34;frequency&#34; &gt;
  &lt;label for=&#34;displayFrequency&#34;&gt;Frequency&lt;/label&gt;
  &lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;noscript&gt;
  Unfortunately, JavaScript is needed to run this. The only JS running on my site is for the projects (no tracking, etc) if you want to enable it. If not, I totally understand :)
&lt;p&gt;&lt;a href=&#34;440hz.png&#34;&gt;Here&amp;rsquo;s what the pitch detection looks like.&lt;/a&gt;
&lt;/noscript&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id=&#34;how-it-works&#34;&gt;How it works&lt;/h1&gt;
&lt;p&gt;This basic app relies on the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API&#34;&gt;Web Audio API&lt;/a&gt;, a powerful module in our browsers that lets us directly work with noise with JavaScript.  I also leveraged this when making &lt;a href=&#34;https://alexanderell.is/posts/morse-codle/&#34;&gt;Morse Codle&lt;/a&gt; and &lt;a href=&#34;https://alexanderell.is/posts/morse-code/&#34;&gt;my Morse code games&lt;/a&gt;, which you can read about &lt;a href=&#34;https://alexanderell.is/posts/writing-morse-code-games/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Web Audio API works by connecting a series of nodes to an &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/AudioContext&#34;&gt;AudioContext&lt;/a&gt;. For this setup, I wanted to connect the user&amp;rsquo;s microphone as an incoming stream to an AudioContext. I also wanted to attach an &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode&#34;&gt;AnalyserNode&lt;/a&gt; to the stream, which would allow me to access real-time data about the stream.&lt;/p&gt;
&lt;p&gt;This is all pretty straightforward, especially if we follow some existing examples from MDN on visualizing the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API&#34;&gt;Web Audio API&lt;/a&gt;. First, we can access the microphone via &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia&#34;&gt;getUserMedia&lt;/a&gt;. We can check if getUserMedia is valid and, if so, try to access the user&amp;rsquo;s microphone, which creates the permissions check for the user. If that&amp;rsquo;s successful, then we can connect our nodes and start the application.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;var source;
var audioContext = new (window.AudioContext || window.webkitAudioContext)();
var analyser = audioContext.createAnalyser();
analyser.minDecibels = -100;
analyser.maxDecibels = -10;
analyser.smoothingTimeConstant = 0.85;
if (!navigator?.mediaDevices?.getUserMedia) {
  // No audio allowed
  alert(&amp;#39;Sorry, getUserMedia is required for the app.&amp;#39;)
  return;
} else {
  var constraints = {audio: true};
  navigator.mediaDevices.getUserMedia(constraints)
    .then(
      function(stream) {
        // Initialize the SourceNode
        source = audioContext.createMediaStreamSource(stream);
        // Connect the source node to the analyzer
        source.connect(analyser);
        visualize();
      }
    )
    .catch(function(err) {
      alert(&amp;#39;Sorry, microphone permissions are required for the app. Feel free to read on without playing :)&amp;#39;)
    });
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For the main application loop via &lt;code&gt;visualize&lt;/code&gt;, we want to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Draw the sound data on the canvas&lt;/li&gt;
&lt;li&gt;Process the incoming sound data to calculate the frequency&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;drawing-the-sound-data-on-the-canvas&#34;&gt;Drawing the sound data on the canvas&lt;/h3&gt;
&lt;p&gt;Because I was more interested in the processing side, I leaned on the examples in the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API&#34;&gt;MDN Visualizations with Web Audio API page&lt;/a&gt;, particularly the examples from the Voice-change-O-matic. You can see the code snippets I borrowed &lt;a href=&#34;https://github.com/mdn/voice-change-o-matic/blob/gh-pages/scripts/app.js#L123-L167&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;processing-the-incoming-sound-data&#34;&gt;Processing the incoming sound data&lt;/h3&gt;
&lt;p&gt;The real difference between that example app and this one is that we want to calculate the main frequency of the incoming sound, and that&amp;rsquo;s where the interesting part of it is.&lt;/p&gt;
&lt;h4 id=&#34;can-we-just-use-the-analysernodes-getbytefrequencydata&#34;&gt;Can we just use the AnalyserNode&amp;rsquo;s getByteFrequencyData?&lt;/h4&gt;
&lt;p&gt;The AnalyserNode actually already has all of the data we need, and it even exposes the &lt;code&gt;getByteFrequencyData&lt;/code&gt; method, which populates a buffer with the frequency bucket intensity level via &lt;a href=&#34;https://en.wikipedia.org/wiki/Fast_Fourier_transform&#34;&gt;Fast Fourier transform&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main problem is that if we&amp;rsquo;re trying to get a specific frequency, we can&amp;rsquo;t just look at the buckets (shout out to &lt;a href=&#34;https://stackoverflow.com/questions/44502536/determining-frequencies-in-js-audiocontext-analysernode&#34;&gt;this stackoverflow question&lt;/a&gt;). If we have a FFT size of 2048, sample rate of 48,000 Hz, our AnalyserNode will generate frequency bins across the range from [0Hz, 24000Hz], with each bin being &lt;code&gt;max_frequency / frequencyBinCount&lt;/code&gt;, where &lt;code&gt;frequencyBinCount&lt;/code&gt; is FFT size divided by 2. In this case, the bins would &lt;code&gt;24000 / 1024 == 23.4Hz&lt;/code&gt; wide. In other words:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;fData[0] is the strength of frequencies from 0 to 23.4Hz.
fData[1] is the strength of frequencies from 23.4Hz to 46.8Hz.
fData[2] is the strength of frequencies from 46.8Hz to 70.2Hz.
fData[3] is the strength of frequencies from 70.2Hz to 93.6Hz.
...
fData[511] is the strength of frequencies from 11976.6Hz to 12000Hz.
fData[512] is the strength of frequencies from 12000Hz to 12023.4Hz.
...
fData[1023] is the strength of frequencies from 23976.6Hz to 24000Hz.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The issue here is that these buckets wouldn&amp;rsquo;t be exact enough to differentiate between notes in the lower ranges (you can see the note to frequency mapping &lt;a href=&#34;https://pages.mtu.edu/~suits/notefreqs.html&#34;&gt;here&lt;/a&gt;). For example, if we want to tune the top string of our guitar in the regular tuning, E, we would want to tune it close to 82.41 Hz (assuming we want the exact match - we won&amp;rsquo;t be getting into any more advanced theory here). If we want to tune it to F instead, we would want to tune it to 87.3 Hz. If all we know about the sound of the string is that it&amp;rsquo;s in the 70.2-93.6Hz bucket, we wouldn&amp;rsquo;t be able to tell which one we&amp;rsquo;re closer to.&lt;/p&gt;
&lt;p&gt;One way to get around this would be to decrease bucket size by increasing the FFT size - we could do this, but our calculations would be more expensive, we&amp;rsquo;d require more space for this, and we&amp;rsquo;d have to bump the size up 10-fold to even get closer to to fine-tuned distances. Additionally, we don&amp;rsquo;t actually need these small bucket sizes for the higher frequency notes, which means we would have a ton of wasted bucket space.&lt;/p&gt;
&lt;h4 id=&#34;autocorrelation-we-can-do-some-quick-math-ourselves&#34;&gt;Autocorrelation: we can do some quick math ourselves&lt;/h4&gt;
&lt;p&gt;Instead of relying on the &lt;code&gt;getByteFrequencyData&lt;/code&gt;, we can instead access the sound information directly and calculate the frequency ourselves. One simple way to do this is autocorrelation, a technique where we compare part of a signal, in this case a sound wave, with a delayed copy of itself. By looking at how the signal compares to itself with a given offset, then varying that offset, we can calculate the offset at which the pattern roughly repeats. That would represent the period of the sound wave, and we could easily then calculate the frequency.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a very simple data series representing the signal at different time steps:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[0.0, 0.5, 1, 0.5, 0.0, -0.5, -1, -0.5, 0.0, 0.5, 1, 0.5, 0.0, -0.5, -1, -0.5]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That might look something like this, where the x-axis is time and the y-axis is amplitude:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;time-series.png&#34; alt=&#34;Basic graph of the above time series&#34;&gt;&lt;/p&gt;
&lt;p&gt;We want to compare this signal against what it would look like at different offsets, for example, with offsets 1 and 2:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Original: [ 0.0,  0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5,  0.0,  0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5]
Offset 1: [       0.0, 0.5, 1.0, 0.5,  0.0, -0.5, -1.0, -0.5,  0.0, 0.5, 1.0, 0.5,  0.0, -0.5, -1.0]
Offset 2: [            0.0, 0.5, 1.0,  0.5,  0.0, -0.5, -1.0, -0.5, 0.0, 0.5, 1.0,  0.5,  0.0, -0.5]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can kind of imagine it like this, with multiple graphs at offsets 1 and 2 respectively:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;time-series-offsets-visualized.png&#34; alt=&#34;Graph of the above time series and additional graphs of the original shifted to the right by 1 and 2 time steps, respectively&#34;&gt;&lt;/p&gt;
&lt;p&gt;To see where our graph repeats itself, we can look at each potential offset value and look at what it would look like if we multiplied each point on the graph by its offset value. Because we&amp;rsquo;re expecting a sinusoidal wave, the sum of those products would be highest when the wave repeats itself, as it would basically be squaring each value.&lt;/p&gt;
&lt;p&gt;For example, for Offset 2, we would multiply each original value by the value of the offset graph:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Original: [ 0.0,  0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5,  0.0,  0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5]
Offset 2: [            0.0, 0.5, 1.0,  0.5,  0.0, -0.5, -1.0, -0.5, 0.0, 0.5, 1.0,  0.5,  0.0, -0.5]
Product:  [            0.0, .25, 0.0, -.25,  0.0,  .25,  0.0, -.25, 0.0, .25, 0.0, -.25,  0.0,  .25]
Sum: 0.25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or, another way to think about this, we would multiple each value in the original by its value two timesteps later:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;offset-2-product.png&#34; alt=&#34;Basic graph of the above time series with a line to the value 2 timesteps later for each&#34;&gt;&lt;/p&gt;
&lt;p&gt;This yields a total of 0.25 for this offset - not a strong contender!&lt;/p&gt;
&lt;p&gt;You could imagine doing this for each offset. At some point, if our wave is regular, we&amp;rsquo;ll reach the point where the wave repeats itself. In this case, with offset 8, we would be multiplying the points by themselves, as the full offset means the graph repeats.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-none&#34; data-lang=&#34;none&#34;&gt;Original: [ 0.0,  0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5,  0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5]
Offset 8: [                                              0.0, 0.5, 1.0, 0.5, 0.0, -0.5, -1.0, -0.5]
Product:  [                                              0.0, .25, 1.0, .25, 0.0,  .25,  1.0,  .25]
Sum: 3.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&#34;offset-8.png&#34; alt=&#34;Basic graph of the above time series multiplied by its value 8 timesteps later, which is the same value&#34;&gt;&lt;/p&gt;
&lt;p&gt;With a sum of 3, this would be the largest sum, and we&amp;rsquo;d know that our wave repeats itself every 8 timesteps. If our total time data length is 1 second cut into 15 time steps, then our frequency would be 15 / 8 = 1.875 Hz.&lt;/p&gt;
&lt;p&gt;What would this look like in code? We could do the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Start with a bufferLength of the analyser&amp;#39;s FFT size
var bufferLength = analyser.fftSize;
// Actually create the buffer
var buffer = new Float32Array(bufferLength);
// Populate the buffer with the time domain data
analyser.getFloatTimeDomainData(buffer);

// Create a new array of the sums of offsets to do the autocorrelation
var offsetSums = new Array(bufferLength).fill(0);
// For each potential offset, calculate the sum of each buffer value times its offset value
for (let offset = 0; offset &amp;lt; bufferLength; offset++) {
  for (let j = 0; j &amp;lt; SIZE - i; j++) {
    offsetSums[offset] = offsetSums[offset] + buffer[j] * buffer[j+offset]
  }
}

// Calculate the offset with the highest value
var maxValue = -1;
var bestOffset = -1;
for (var i = 0; i &amp;lt; offsetSums.length; i++) {
  if (offsetSums[i] &amp;gt; maxValue) {
    maxValue = offsetSums[i];
    bestOffset = i;
  }
}

// Once we have the best offset for the repetition, we can calculate the frequency from the sampleRate
var frequency = sampleRate / bestOffset
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s pretty much it! All that&amp;rsquo;s left is some UX to smooth the display changing (we don&amp;rsquo;t want to flash a new value every animation frame, though you can try it above) and translate the frequency into a note.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;440hz.png&#34; alt=&#34;Screenshot of the  pitch detection with a 440Hz tuning fork.&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can see the full source code &lt;a href=&#34;tuner.js&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;can-we-do-better&#34;&gt;Can we do better?&lt;/h4&gt;
&lt;p&gt;One of the drawbacks of this basic autocorrelation is that it has trouble with sounds with multiple stacked notes - think of a piano note that actually has that same note layered across different octaves. The basic detector does well with my 440Hz tuning fork, but when I try to tune the A string on my guitar and play a more complicated sound wave (the guitar string vibrating), it will switch between detecting the 110Hz tone and 440Hz, depending on how close I am to the microphone. Another drawback is that it&amp;rsquo;s not as efficient as it could be; since we&amp;rsquo;re calculating each offset across the entire sample, leading to quadratic complexity.&lt;/p&gt;
&lt;p&gt;There are also many ways to tune this basic detection further, such as trimming the sound data to only run on interesting parts of the tone and doing parabolic interpolation, where we assume that our peak isn&amp;rsquo;t a sharp point on the graph but instead a smooth parabola between the points on either side - we can use the highest point of the parabola for even more accuracy.&lt;/p&gt;
&lt;p&gt;I chose this basic approach because 1) it&amp;rsquo;s easy to implement and follow along with the code and 2) it works pretty well for my use case. If you&amp;rsquo;re interested in more of the intricacies, I highly recommend checking out the &lt;a href=&#34;https://en.wikipedia.org/wiki/Pitch_detection_algorithm&#34;&gt;pitch detection algorithm&lt;/a&gt; page on Wikipedia; definitely a rabbit hole!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This will eventually be part of an interactive ear training app, where you will listen to a tone and have to repeat it — this pitch detection will be the backbone of processing the incoming sound and translating it to the note.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a good start for now!&lt;/p&gt;
&lt;script src=&#34;tuner.js&#34;&gt;&lt;/script&gt;</description>
    </item>
    
    <item>
      <title>I prefer my biographies in chronological order, thank you very much</title>
      <link>https://alexanderell.is/posts/chrono-bio/</link>
      <pubDate>Sun, 13 Mar 2022 18:52:21 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/chrono-bio/</guid>
      <description>&lt;p&gt;Usually, biographies go through someone&amp;rsquo;s story in roughly chronological order.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;linear.png&#34; alt=&#34;A linear graph of a biography with subject&amp;amp;rsquo;s age on the x-axis and page # on the y-axis&#34;&gt;&lt;/p&gt;
&lt;p&gt;OK, that&amp;rsquo;s a little too linear, like a page-a-day journal for your entire life. Realistically, you&amp;rsquo;re going to focus on different parts of the subject&amp;rsquo;s life more than others.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;realistic-linear.png&#34; alt=&#34;A monotonically increasing line on a graph of a biography with subject&amp;amp;rsquo;s age on the x-axis and page # on the y-axis&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can imagine that broken up into chapters, too. Maybe our subject&amp;rsquo;s childhood is generally passed over in favor of the more interesting middle-life.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;realistic-linear-with-chapters.png&#34; alt=&#34;A monotonically increasing line on a graph of a biography with subject&amp;amp;rsquo;s age on the x-axis and page # on the y-axis, broken up into a few chapters along the line&#34;&gt;&lt;/p&gt;
&lt;p&gt;This general flow makes sense, because you can do some fun foreshadowing by mentioning things that happen in the subject&amp;rsquo;s future (orange in the diagram below), and you can remind the reader of things that happened in the subject&amp;rsquo;s past with past references (purple in the diagram below). This makes sense, since you, the reader, are generally learning about things in the subject&amp;rsquo;s life as they happen.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;realistic-linear-with-references.png&#34; alt=&#34;A monotonically increasing line on a graph of a biography with subject&amp;amp;rsquo;s age on the x-axis and page # on the y-axis, with lines pointing towards later in the story representing foreshadowing and lines pointing back representing past references.&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I recently read a &lt;a href=&#34;https://www.goodreads.com/en/book/show/43984882-edison&#34;&gt;biography of Thomas Edison&lt;/a&gt; that turned this model around. Each chapter was a decade of Edison&amp;rsquo;s life, but it was as if you chopped the chronological timeline into chunks and reversed them. Here&amp;rsquo;s what the contents page looks like:&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;edison-biography-contents.png&#34; alt=&#34;A screenshot of the book&amp;amp;rsquo;s contents page, which shows each chapter devoted to a decade of Edison&amp;amp;rsquo;s life&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;You could imagine us drawing this like before, roughly like the following, where the first chapter was Edison as an old man and the last chapter is about the years following Edison&amp;rsquo;s birth.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;edison-timeline.png&#34; alt=&#34;Edison&amp;amp;rsquo;s timeline, with an oddly disjointed graph as the chapters jump decades&#34;&gt;&lt;/p&gt;
&lt;p&gt;If we drew in the line following from birth to death, we&amp;rsquo;d have to have these dotted line jumps from the end of the chapter to the beginning of the previous chapter:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;edison-timeline-with-dotted.png&#34; alt=&#34;Edison&amp;amp;rsquo;s timeline, with the disjointed graph connected with dotted lines as the chapters jump decades&#34;&gt;&lt;/p&gt;
&lt;p&gt;This meant that those two I mentioned earlier, foreshadowing in orange (talking about events that happen later in Edison&amp;rsquo;s life) and past references in purple (talking about events that happened earlier in Edison&amp;rsquo;s life), looked a little odd, like the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;edison-timeline-with-references.png&#34; alt=&#34;Edison&amp;amp;rsquo;s timeline with the disjointed graph and additional lines showing foreshadowing and past references, although they go between chapters in weird ways&#34;&gt;&lt;/p&gt;
&lt;p&gt;This meant that some references to things in Edison&amp;rsquo;s past were actually pointers to later parts of the book, while some of the forward-looking foreshadowing referred to parts you had already read. Not all references were switched, though, since time progressed forward within each chapter.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This made for some initial confusion on my part when reading it at first. Edison would have a relationship with a person that I hadn&amp;rsquo;t met yet, or there would be a reference to a chronologically-earlier but book-later event that I didn&amp;rsquo;t yet know about. It also made the chapter transitions feel like sudden cuts, as you&amp;rsquo;d meander from 1900 to 1909 throughout the course of a chapter, then suddenly spring back to 1890 at the start of the next chapter, suddenly faced with making your way back towards 1899.&lt;/p&gt;
&lt;p&gt;As much as I joke that a wayward junior editor mixed up the chapter order in the final manuscript right before it went to the printers, I do think this approach adds to the story in an interesting way.  It was a little like learning about your grandparent&amp;rsquo;s early life, as they already exist in your mind as grown adults, but learning about their childhood adds depth beyond the adult you know. This nonlinear splitting also meant that some &amp;ldquo;foreshadowing&amp;rdquo; was even more interesting, as you could see the ripples of this curious young man&amp;rsquo;s mind having already flowed through to future inventions with his later life already established in your head.&lt;/p&gt;
&lt;p&gt;Nonlinear storytelling can be such an interesting component of a story, where the jumbled timeline plays with our expectations of story and causality. Consider &lt;a href=&#34;https://www.imdb.com/title/tt0209144/&#34;&gt;Memento&lt;/a&gt; or &lt;a href=&#34;https://www.goodreads.com/book/show/4981.Slaughterhouse_Five&#34;&gt;Slaughterhouse-Five&lt;/a&gt; and how their nonlinearity adds such depth and flavor. As dismissive as I may sound in the title, I do think this was an interesting approach I hadn&amp;rsquo;t seen much before (though I&amp;rsquo;m far from an expert in the art of biography), but in this case, I think I would have enjoyed this shift more if I had already read another detailed Edison biography. With my mind pre-seeded with the character list and the major events, it would have been easier to follow the threads and connect the dots.&lt;/p&gt;
&lt;p&gt;I look forward to seeing how it feels when I come back to it in the future.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>My lizard brain is no match for infinite scroll</title>
      <link>https://alexanderell.is/posts/infinite-scroll/</link>
      <pubDate>Sun, 06 Mar 2022 17:28:14 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/infinite-scroll/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m declaring defeat. Infinite scroll has won.&lt;/p&gt;
&lt;p&gt;There is an endless supply of interesting things to consume online, and there are teams and teams of very smart people working very hard to get me to keep watching. One more video means one more metric counter incrementing, one more engagement click, one more step towards growth, ad revenue, product success&amp;hellip;&lt;/p&gt;
&lt;p&gt;The danger for me is that I fall in. It&amp;rsquo;s so easy to open an app in an automatic movement of muscle memory, filling up an empty moment without conscious thought. Suddenly that content is there, and it&amp;rsquo;s so easy to go to the next image, post, or video. 30 seconds later, once you&amp;rsquo;ve reached the end, they&amp;rsquo;ll even scroll to the next one for you.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s so easy to get stuck in it. Time melts away. I wasn&amp;rsquo;t planning to spend 30 minutes lying on the couch looking at my phone, but here we are.  It&amp;rsquo;s far too easy, and since they know exactly what I find interesting, there&amp;rsquo;s almost no barrier to keep looking.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I think my main problem with it is that it makes me a passive recipient in my interactions with this device, instead of an active participant. I didn&amp;rsquo;t know I wanted to see how these hand-made skis are made and finished, but then again, I also didn&amp;rsquo;t know I&amp;rsquo;d be down here on the couch for 45 minutes.&lt;/p&gt;
&lt;p&gt;When I take a minute to think about the things I enjoy doing with my devices, it helps me realize that they&amp;rsquo;re the ones where I&amp;rsquo;m deliberately using it. Talking to people I know, for example. Watching that movie I had been looking forward to. Looking up the origin of an oddly spelled word. Creating, rather than just consuming, and using it as a tool to improve my life, even if that little improvement is a one word answer to a tiny question that had been bugging me.&lt;/p&gt;
&lt;p&gt;But the content — it&amp;rsquo;s a default. It&amp;rsquo;s a comfort when you&amp;rsquo;re tired, when it&amp;rsquo;s just easier to look. Worse yet, by defaulting to this little screen, it gets in the way of me spending my time deliberately. I really enjoy drawing, and usually, even if I&amp;rsquo;m tired, if I grab a pencil and some paper, I end up having so much fun scratching out little sketches. If I had gotten up and started drawing, I probably would have remembered how fun it is. But, I&amp;rsquo;m a little tired. Going for a walk sounds tiring, and it&amp;rsquo;s not quite bedtime yet, so I&amp;rsquo;ll just take a glance at a few more videos since I&amp;rsquo;m already here. My feet are a little cold from being here on the couch for an hour, but luckily I&amp;rsquo;ve got a blanket as well.&lt;/p&gt;
&lt;p&gt;Leisure time is so important, and I have no time for hustle culture. My sacred down time is under attack by these patterns, all from apps that want me spending more time on them with plenty of ways to hook me in. I enjoy turning off my brain and relaxing, but I&amp;rsquo;ve noticed it&amp;rsquo;s so much better when it&amp;rsquo;s on my terms. When I think about how I feel after 30 minutes of letting my mind wander on a walk versus 30 minutes of scrolling, it&amp;rsquo;s night and day.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I don&amp;rsquo;t have any quick fixes or easy answers. I&amp;rsquo;ve struggled with this for a very long time. I&amp;rsquo;ve gotten much better at dealing with it, but I find I have to remain conscious of it. That&amp;rsquo;s where admitting defeat helps; I know how my brain works, and I can work with it. Let&amp;rsquo;s not install that app with the infinite scroll, since we can probably get by with just the mobile web version. Let&amp;rsquo;s not log in, unless there&amp;rsquo;s a reason you need to, since they&amp;rsquo;re after you with recommendations for your account. Let&amp;rsquo;s try to be conscious of how much time you end up spending on certain sites.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a broad pattern these days of mobile sites being much worse and more restrictive, since they&amp;rsquo;re trying to push you towards logging in or downloading the app. Ironically, being conscious of my struggle, these moments of friction actually help push me towards closing the site instead. For that, I&amp;rsquo;m grateful for Reddit&amp;rsquo;s mobile UI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;reddit.png&#34; alt=&#34;Screenshot showing Reddit&amp;amp;rsquo;s mobile UI telling me I can download the app to use infinite scroll&#34;&gt;&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t think I&amp;rsquo;ll be downloading the app today.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>I miss easily seeing how my coworkers use tools</title>
      <link>https://alexanderell.is/posts/casual-collaboration/</link>
      <pubDate>Sun, 27 Feb 2022 13:58:57 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/casual-collaboration/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s one of the little things I miss about working in-person with other people.&lt;/p&gt;
&lt;p&gt;Writing software can involve so many different tools. Before my current remote working times, one of the ways I picked up on efficient ways to use some of the tools involved was from seeing how coworkers navigated them, especially when they had a lot more practice with them.&lt;/p&gt;
&lt;p&gt;A colleague and I would be looking at their screen together, working with a tool that I had an OK understanding of, when they&amp;rsquo;d use a feature or shortcut I didn&amp;rsquo;t even know about. It may have been muscle memory to them, but it would be brand new to me.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Wait, how did you just do that?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the most staggering things about ramping up at Google was the number of internal tools and systems that were brand new to me. I learned many of them by trying them out and following patterns of how I saw others using them, slowly over time building my familiarity with them until things became second nature. Even after I was comfortable, there would still be power-user techniques that I only picked up on from seeing a coworker do them on their screen.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What button was that again?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve noticed that this discoverability is harder for me since I&amp;rsquo;ve been working remotely.  It still happens as we share screens, record talks, and screenshot pages and buttons for each other, but it has never felt quite as natural.  Maybe because there&amp;rsquo;s some inherent cognitive load to screen sharing, requiring at least a few mental cycles to even just keep up with what someone is doing.  Add in any delays, sudden window changes, or small fonts, and it&amp;rsquo;s even trickier.&lt;/p&gt;
&lt;p&gt;Maybe it&amp;rsquo;s that we often only share a single screen or window, with text blown up so we can all see in our browser windows or Zoom clients. I wonder if having the exact same monitor setup as the other person and having their monitors mirrored on mine would help, since I would have the full context of what they&amp;rsquo;re doing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sorry, do you mind zooming in a bit?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There may also be some natural barrier in my head to looking at a screen together remotely. It&amp;rsquo;s probably latent Zoom fatigue, but there&amp;rsquo;s a difference between a text chat back and forth and hopping on a call. Working in-person, it&amp;rsquo;s likely that a discussion would be us talking about it already, so maybe the jump to &amp;ldquo;can I come see what error you&amp;rsquo;re getting?&amp;rdquo; doesn&amp;rsquo;t have as much overhead to it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve actively worked to push against this barrier. When working with more junior engineers or interns, I would force myself to remove it completely for them, jumping straight into chats and debugging without hesitation. For my regular individual contributor work with others, though, I still feel it, and I always try to stay conscious of it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to hop on a quick call?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I think about this discoverability a lot for junior engineers or new team members ramping up. There are so many things to learn, and reducing barriers to sharing that knowledge can only help. As we continue to move forward with widespread remote work, how do we better enable easy and casual collaboration?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Morse Codle</title>
      <link>https://alexanderell.is/posts/morse-codle/</link>
      <pubDate>Wed, 16 Feb 2022 21:03:02 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/morse-codle/</guid>
      <description>&lt;noscript&gt;
  This is a game for playing Wordle with Morse Code.


  JavaScript is needed to run this game.  If you don&#39;t have it enabled, I totally get it — I &lt;a href=&#34;https://alexanderell.is/posts/taking-over-my-clipboard/&#34; target=&#34;blank_&#34;&gt;often&lt;/a&gt; &lt;a href=&#34;https://alexanderell.is/posts/attention-javascript&#34; target=&#34;blank_&#34;&gt;don&#39;t like&lt;/a&gt; it either.
&lt;/noscript&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;styles.css&#34;&gt;

&lt;div id=&#34;game&#34;&gt;
  &lt;div id=&#34;title-bar&#34;&gt;
      &lt;div id=&#34;help-icon&#34; class=&#34;icon&#34;&gt;
      &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; width=&#34;24&#34;&gt;
        &lt;path fill=&#34;var(--color-tone-1)&#34; d=&#34;M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z&#34;&gt;&lt;/path&gt;
      &lt;/svg&gt;
    &lt;/div&gt;

    &lt;div id=&#34;morse-codle-title&#34;&gt;
      MORSE CODLE
    &lt;/div&gt;

    &lt;div id=&#34;options-icon&#34; class=&#34;icon&#34;&gt;
      &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; width=&#34;24&#34;&gt;
        &lt;path fill=&#34;var(--color-tone-1)&#34; d=&#34;M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z&#34;&gt;&lt;/path&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id=&#34;play-button-container&#34;&gt;
    &lt;div id=&#34;play-button&#34; class=&#34;icon&#34;&gt;
      &lt;svg version=&#34;1.1&#34; id=&#34;Capa_1&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; xmlns:xlink=&#34;http://www.w3.org/1999/xlink&#34; x=&#34;0px&#34; y=&#34;0px&#34;
          fill=&#34;var(--color-tone-2)&#34; viewBox=&#34;0 0 60 60&#34; style=&#34;enable-background:new 0 0 60 60;&#34; xml:space=&#34;preserve&#34;&gt;
      &lt;g&gt;
        &lt;path d=&#34;M45.563,29.174l-22-15c-0.307-0.208-0.703-0.231-1.031-0.058C22.205,14.289,22,14.629,22,15v30
          c0,0.371,0.205,0.711,0.533,0.884C22.679,45.962,22.84,46,23,46c0.197,0,0.394-0.059,0.563-0.174l22-15
          C45.836,30.64,46,30.331,46,30S45.836,29.36,45.563,29.174z M24,43.107V16.893L43.225,30L24,43.107z&#34;/&gt;
        &lt;path d=&#34;M30,0C13.458,0,0,13.458,0,30s13.458,30,30,30s30-13.458,30-30S46.542,0,30,0z M30,58C14.561,58,2,45.439,2,30
          S14.561,2,30,2s28,12.561,28,28S45.439,58,30,58z&#34;/&gt;
      &lt;/g&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;div id=&#34;board-container&#34;&gt;
    &lt;div id=&#34;board&#34;&gt;
      &lt;div class=&#34;game-row&#34;&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;game-row&#34;&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;game-row&#34;&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;game-row&#34;&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
        &lt;div class=&#34;game-tile&#34; data-state=&#34;empty&#34;&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;


  &lt;div id=&#34;keyboard&#34;&gt;
    &lt;div class=&#34;row&#34;&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;q&#34;&gt;q&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;w&#34;&gt;w&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;e&#34;&gt;e&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;r&#34;&gt;r&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;t&#34;&gt;t&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;y&#34;&gt;y&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;u&#34;&gt;u&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;i&#34;&gt;i&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;o&#34;&gt;o&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;p&#34;&gt;p&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&#34;row&#34;&gt;
      &lt;div class=&#34;spacer half&#34;&gt;&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;a&#34;&gt;a&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;s&#34;&gt;s&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;d&#34;&gt;d&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;f&#34;&gt;f&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;g&#34;&gt;g&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;h&#34;&gt;h&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;j&#34;&gt;j&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;k&#34;&gt;k&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;l&#34;&gt;l&lt;/div&gt;
      &lt;div class=&#34;spacer half&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&#34;row&#34;&gt;
      &lt;div id=&#34;enter&#34; class=&#34;one-and-a-half key&#34; data-key=&#34;↵&#34;&gt;Enter&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;z&#34;&gt;z&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;x&#34;&gt;x&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;c&#34;&gt;c&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;v&#34;&gt;v&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;b&#34;&gt;b&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;n&#34;&gt;n&lt;/div&gt;
      &lt;div class=&#34;key&#34; data-key=&#34;m&#34;&gt;m&lt;/div&gt;
      &lt;div id=&#34;delete&#34; class=&#34;one-and-a-half key&#34; data-key=&#34;←&#34;&gt;
        &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; width=&#34;24&#34;&gt;
          &lt;path fill=&#34;var(--color-tone-1)&#34; d=&#34;M22 3H7c-.69 0-1.23.35-1.59.88L0 12l5.41 8.11c.36.53.9.89 1.59.89h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H7.07L2.4 12l4.66-7H22v14zm-11.59-2L14 13.41 17.59 17 19 15.59 15.41 12 19 8.41 17.59 7 14 10.59 10.41 7 9 8.41 12.59 12 9 15.59z&#34;&gt;&lt;/path&gt;
        &lt;/svg&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;

  &lt;!-- Help Modal --&gt;
  &lt;!-- &lt;div id=&#34;overlay-modal&#34; class=&#34;overlay-modal&#34; open&gt; --&gt;
  &lt;div id=&#34;overlay-modal&#34; class=&#34;overlay-modal&#34;&gt;
    &lt;div class=&#34;modal-content&#34;&gt;
      &lt;div id=&#34;close-icon&#34; class=&#34;icon&#34;&gt;
      &lt;svg class=&#34;game-icon&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; width=&#34;24&#34;&gt;
        &lt;path fill=&#34;var(--color-tone-1)&#34; d=&#34;M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z&#34;&gt;&lt;/path&gt;
      &lt;/svg&gt;
      &lt;/div&gt;
      &lt;div class=&#34;instructions&#34;&gt;
        &lt;p&gt;Guess the &lt;strong&gt;MORSE CODLE&lt;/strong&gt; in four tries.&lt;/p&gt;
        &lt;p&gt;Hit space or press the play button to hear the word. Each guess must be a valid five-letter word. Hit the enter button to submit.&lt;/p&gt;
        &lt;p&gt;After each guess, the color of the tiles will change to show how close your guess was to the word, and the tile will show the Morse code for that letter.&lt;/p&gt;
        &lt;div class=&#34;examples&#34;&gt;
          &lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/p&gt;
          &lt;div class=&#34;example&#34;&gt;
            &lt;div class=&#34;board-row&#34;&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;w&#34; data-state=&#34;correct&#34; reveal=&#34;&#34;&gt;.--&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;e&#34; data-state=&#34;tbd&#34;&gt;.&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;a&#34; data-state=&#34;tbd&#34;&gt;.-&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;r&#34; data-state=&#34;tbd&#34;&gt;.-.&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;y&#34; data-state=&#34;tbd&#34;&gt;-.--&lt;/div&gt;
            &lt;/div&gt;
            &lt;p&gt;The letter &lt;strong&gt;W&lt;/strong&gt; is in the word and in the correct spot.&lt;/p&gt;
          &lt;/div&gt;
          &lt;div class=&#34;example&#34;&gt;
            &lt;div class=&#34;board-row&#34;&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;p&#34; data-state=&#34;tbd&#34;&gt;.--.&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;i&#34; data-state=&#34;present&#34; reveal=&#34;&#34;&gt;..&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;l&#34; data-state=&#34;tbd&#34;&gt;.-..&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;l&#34; data-state=&#34;tbd&#34;&gt;.-..&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;s&#34; data-state=&#34;tbd&#34;&gt;...&lt;/div&gt;
            &lt;/div&gt;
            &lt;p&gt;The letter &lt;strong&gt;I&lt;/strong&gt; is in the word but in the wrong spot.&lt;/p&gt;
          &lt;/div&gt;
          &lt;div class=&#34;example&#34;&gt;
            &lt;div class=&#34;board-row&#34;&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;v&#34; data-state=&#34;tbd&#34;&gt;---.&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;a&#34; data-state=&#34;tbd&#34;&gt;.-&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;g&#34; data-state=&#34;tbd&#34;&gt;--.&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;u&#34; data-state=&#34;absent&#34; reveal=&#34;&#34;&gt;..-&lt;/div&gt;
              &lt;div class=&#34;game-tile&#34; letter=&#34;e&#34; data-state=&#34;tbd&#34;&gt;.&lt;/div&gt;
            &lt;/div&gt;
            &lt;p&gt;The letter &lt;strong&gt;U&lt;/strong&gt; is not in the word in any spot.&lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;p&gt;&lt;strong&gt;A new MORSE CODLE will be available each day! You can also enable random mode to get a new word every time you refresh.&lt;strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;&lt;strong&gt;&lt;strong&gt;
      &lt;/strong&gt;&lt;/strong&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;!-- Options Modal --&gt;
&lt;!-- &lt;div id=&#34;options-overlay-modal&#34; class=&#34;overlay-modal&#34; open&gt; --&gt;
&lt;div id=&#34;options-overlay-modal&#34; class=&#34;overlay-modal&#34;&gt;
  &lt;div class=&#34;modal-content&#34;&gt;
    &lt;div id=&#34;options-close-icon&#34; class=&#34;icon&#34;&gt;
      &lt;svg class=&#34;game-icon&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; width=&#34;24&#34;&gt;
        &lt;path fill=&#34;var(--color-tone-1)&#34; d=&#34;M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z&#34;&gt;&lt;/path&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
    &lt;div class=&#34;settings&#34;&gt;
      &lt;div id=&#34;settings-title&#34;&gt;
        Settings
      &lt;/div&gt;
      &lt;div class=&#34;setting&#34;&gt;
        &lt;div class=&#34;text&#34;&gt;
          &lt;div class=&#34;title&#34;&gt;
            Full speed
          &lt;/div&gt;
          &lt;div class=&#34;description&#34;&gt;
            No additional space in between letters
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;switch&#34; id=&#34;speed-switch&#34;&gt;
          &lt;span class=&#34;knob&#34; id=&#34;speed-knob&#34;&gt;&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;setting&#34;&gt;
        &lt;div class=&#34;text&#34;&gt;
          &lt;div class=&#34;title&#34;&gt;
            Hard mode
          &lt;/div&gt;
          &lt;div class=&#34;description&#34;&gt;
            Only one playthrough per guess
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;switch&#34; id=&#34;hard-switch&#34;&gt;
          &lt;span class=&#34;knob&#34; id=&#34;hard-knob&#34;&gt;&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;setting&#34;&gt;
        &lt;div class=&#34;text&#34;&gt;
          &lt;div class=&#34;title&#34;&gt;
            Random mode
          &lt;/div&gt;
          &lt;div class=&#34;description&#34;&gt;
            Instead of using the word of the day, use a new random word every time you refresh the page.
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;switch&#34; id=&#34;random-switch&#34;&gt;
          &lt;span class=&#34;knob&#34; id=&#34;random-knob&#34;&gt;&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;setting&#34;&gt;
        &lt;div class=&#34;text&#34;&gt;
          &lt;div class=&#34;title&#34;&gt;
            Feedback
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;settings-link&#34;&gt;
          &lt;a href=&#34;https://twitter.com/lxmkls&#34; target=&#34;blank_&#34;&gt;
            Twitter
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&#34;setting&#34;&gt;
        &lt;div class=&#34;text&#34;&gt;
          &lt;div class=&#34;title&#34;&gt;
            Questions?
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&#34;settings-link&#34;&gt;
          &lt;a href=&#34;https://alexanderell.is/posts/morse-codle/faq.txt&#34; target=&#34;blank_&#34;&gt;
            FAQ
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;!-- Options Modal --&gt;
&lt;!-- &lt;div id=&#34;options-overlay-modal&#34; class=&#34;overlay-modal&#34; open&gt; --&gt;
&lt;div id=&#34;done-overlay-modal&#34; class=&#34;overlay-modal&#34;&gt;
  &lt;div class=&#34;modal-content&#34;&gt;
    &lt;div id=&#34;done-close-icon&#34; class=&#34;icon&#34;&gt;
    &lt;svg class=&#34;game-icon&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; width=&#34;24&#34;&gt;
      &lt;path fill=&#34;var(--color-tone-1)&#34; d=&#34;M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z&#34;&gt;&lt;/path&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;div class=&#34;done&#34;&gt;
    &lt;div id=&#34;done-title&#34;&gt;&lt;/div&gt;
    &lt;div id=&#34;done-subtitle&#34;&gt;&lt;/div&gt;
    &lt;div id=&#34;done-text&#34;&gt;
      You can wait for tomorrow for a new game, or if you want a new challenge now, you can enable Random mode and refresh the page.
    &lt;/div&gt;
    &lt;div class=&#34;setting&#34;&gt;
      &lt;button id=&#34;share-button&#34;&gt;Share&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;script src=&#34;https://alexanderell.is/posts/morse-codle/text/clipboard-polyfill.text.js&#34;&gt;&lt;/script&gt;

&lt;script src=&#34;constants.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;morse-code.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;morse-codle.js&#34;&gt;&lt;/script&gt;</description>
    </item>
    
    <item>
      <title>Writing Morse code games in JavaScript using the Web Audio API</title>
      <link>https://alexanderell.is/posts/writing-morse-code-games/</link>
      <pubDate>Sat, 12 Feb 2022 12:36:19 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/writing-morse-code-games/</guid>
      <description>&lt;div id=&#34;toc&#34;&gt;
  &lt;nav id=&#34;TableOfContents&#34;&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&#34;#playing-sound-with-the-web-audio-api&#34;&gt;Playing sound with the Web Audio API&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#playing-morse-code&#34;&gt;Playing Morse code&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#morse-code-timing&#34;&gt;Morse code timing&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#playing-letters-words-and-sentences&#34;&gt;Playing letters, words, and sentences&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#adjusting-the-speed&#34;&gt;Adjusting the speed&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#the-listening-game&#34;&gt;The Listening Game&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#the-speaking-game&#34;&gt;The &amp;ldquo;Speaking&amp;rdquo; game&lt;/a&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;a href=&#34;#parsing-the-incoming-signal-into-morse-code&#34;&gt;Parsing the incoming signal into Morse code&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&#34;#hooking-it-up-to-user-input&#34;&gt;Hooking it up to user input&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&#34;#finishing-thoughts&#34;&gt;Finishing thoughts&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;

&lt;p&gt;Last week, I posted &lt;a href=&#34;https://alexanderell.is/posts/morse-code&#34;&gt;a pair of games I made to play with Morse code in your browser&lt;/a&gt;. I wanted to take some time to describe how I made them and some of the challenges that came up along the way.&lt;/p&gt;
&lt;p&gt;The first game is a listening game, where it plays the Morse code representation of a random target and you have to enter it in. The second game is a &amp;ldquo;speaking&amp;rdquo; game, where it gives you a random target string and you have to enter it in Morse code. The games include some options, including the speed that the Morse code plays (ranging from slow to fast for both the &lt;a href=&#34;https://www.google.com/search?q=Farnsworth+method&#34;&gt;Farnsworth method&lt;/a&gt; and extending all times) and the difficulty (with the target ranging from a single letter to a number of words).&lt;/p&gt;
&lt;p&gt;These games are written in vanilla JavaScript, and the source code is available &lt;a href=&#34;https://alexanderell.is/posts/morse-code/morse-code.js&#34;&gt;here&lt;/a&gt; and &lt;a href=&#34;https://alexanderell.is/posts/morse-code/constants.js&#34;&gt;here&lt;/a&gt;. It&amp;rsquo;s unpolished, but it works :)&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;playing-sound-with-the-web-audio-api&#34;&gt;Playing sound with the Web Audio API&lt;/h2&gt;
&lt;p&gt;The first thing that comes to mind for these games is the ability to play sound. You can do so many things with JavaScript, and luckily, this is one of them. The main interface for sound in the browser is &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API&#34;&gt;the Web Audio API&lt;/a&gt;. I won&amp;rsquo;t go into too much detail about it here, but I highly recommend the MDN docs and the tutorials there for playing around with it.&lt;/p&gt;
&lt;p&gt;For both of these games, I want to be able to play a single tone for a certain amount of time. The first step is playing the tone, and we can worry about the times later. One way to do this is to set up a new &lt;code&gt;AudioContext&lt;/code&gt; (&amp;ldquo;an audio-processing graph built from audio modules linked together&amp;rdquo;) with an &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode&#34;&gt;&lt;code&gt;OscillatorNode&lt;/code&gt;&lt;/a&gt; (&amp;ldquo;an AudioNode audio-processing module that causes a given frequency of wave to be created&amp;rdquo;) and a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/GainNode&#34;&gt;&lt;code&gt;GainNode&lt;/code&gt;&lt;/a&gt; (&amp;ldquo;an AudioNode audio-processing module that causes a given gain to be applied to the input data before its propagation to the output&amp;rdquo;) for controlling volume. Simply put, we&amp;rsquo;ll want to be able to create a tone at a certain frequency using the &lt;code&gt;OscillatorNode&lt;/code&gt;, and we&amp;rsquo;ll want to start and stop the tone by adjusting the volume with the &lt;code&gt;GainNode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Before we do anything, we&amp;rsquo;ll need to initialize the &lt;code&gt;AudioContext&lt;/code&gt;, add the nodes, and connect them to the context. We can set up the &lt;code&gt;OscillatorNode&lt;/code&gt; with a frequency of 440hz, A, which is a reasonably pleasant Morse code tone. This might look something like the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const FREQUENCY = 440;

let note_context;
let note_node;
let gain_node;
let audioContextInitialized = false;

function initializeAudioContext() {
  note_context = new AudioContext();
  note_node = note_context.createOscillator();
  gain_node = note_context.createGain();
  note_node.frequency.value = FREQUENCY.toFixed(2);
  gain_node.gain.value = 0;
  note_node.connect(gain_node);
  gain_node.connect(note_context.destination);
  note_node.start();
  audioContextInitialized = true;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With that audio context, we can then make functions for starting and stopping playing, which really adjust the gain value in the &lt;code&gt;GainNode&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function startNotePlaying() {
  // Pass a start time of 0 so it starts ramping up immediately.
  gain_node.gain.setTargetAtTime(0.1, 0, 0.001)
}

function stopNotePlaying() {
  // Pass a start time of 0 so it starts ramping down immediately.
  gain_node.gain.setTargetAtTime(0, 0, 0.001)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Additionally, we&amp;rsquo;ll want to keep track of whether or not the audio context is initialized, so the first time we&amp;rsquo;re planning to play a sound we can initialize it if it hasn&amp;rsquo;t been already. As a side note, for mobile users, it&amp;rsquo;s important that this initialization is tied to a user action; we can&amp;rsquo;t just start playing sound without the user&amp;rsquo;s input (I know nothing about the code involved, but I&amp;rsquo;d bet this is why YouTube videos on mobile Safari are initially muted).&lt;/p&gt;
&lt;p&gt;You can try these out with the following buttons:&lt;/p&gt;
&lt;p&gt;&lt;button id=&#34;playNote&#34;&gt;Start Note&lt;/button&gt;
&lt;button id=&#34;stopNote&#34;&gt;Stop Note&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
const FREQUENCY = 440;

let note_context;
let note_node;
let gain_node;
let audioContextInitialized = false;

function initializeAudioContext() {
  note_context = new AudioContext();
  note_node = note_context.createOscillator();
  gain_node = note_context.createGain();
  note_node.frequency.value = FREQUENCY.toFixed(2);
  gain_node.gain.value = 0;
  note_node.connect(gain_node);
  gain_node.connect(note_context.destination);
  note_node.start();
  audioContextInitialized = true;
}

function startNotePlaying() {
  // Pass a start time of 0 so it starts ramping up immediately.
  gain_node.gain.setTargetAtTime(0.1, 0, 0.001)
}

function stopNotePlaying() {
  // Pass a start time of 0 so it starts ramping down immediately.
  gain_node.gain.setTargetAtTime(0, 0, 0.001)
}

document.getElementById(&#39;playNote&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  startNotePlaying();
});
document.getElementById(&#39;stopNote&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  stopNotePlaying();
});
&lt;/script&gt;
&lt;p&gt;Not too bad! The next step is being able to play them for a given amount of time, since Morse code is made up of short signals (dots) and longer signals (dashes). For this, we can do a subtle trick with JavaScript&amp;rsquo;s &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;Promises&lt;/code&gt;. JavaScript doesn&amp;rsquo;t really have a sleep method, but we can simulate one by creating a &lt;code&gt;Promise&lt;/code&gt; that resolves itself after a certain amount of time. We can then &lt;code&gt;await&lt;/code&gt; that function in an &lt;code&gt;async&lt;/code&gt; function; this will cause that &lt;code&gt;async&lt;/code&gt; function to pause at that function call until the amount of time has passed. Because of JavaScript&amp;rsquo;s event loop, it isn&amp;rsquo;t an exact timing, but it&amp;rsquo;s close enough for this!&lt;/p&gt;
&lt;p&gt;To play a note for a certain amount of time, we can start the note playing, wait for the given time, then stop playing the note. That could look something like the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Times in milliseconds; note that Morse code dash time is 3 * dot time
var DOT_TIME = 60;
var DASH_TIME = DOT_TIME * 3;

function sleep(ms) {
  return new Promise(resolve =&amp;gt; setTimeout(resolve, ms));
}

async function playDash() {
  startNotePlaying();
  await sleep(DASH_TIME);
  stopNotePlaying();
}

async function playDot() {
  startNotePlaying();
  await sleep(DOT_TIME);
  stopNotePlaying();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can try these out with the following buttons:&lt;/p&gt;
&lt;p&gt;&lt;button id=&#34;playDot&#34;&gt;Play dot&lt;/button&gt;
&lt;button id=&#34;playDash&#34;&gt;Play dash&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
// Times in milliseconds; note that Morse code dash time is 3 * dot time
var DOT_TIME = 60;
var DASH_TIME = DOT_TIME * 3;

function sleep(ms) {
  return new Promise(resolve =&gt; setTimeout(resolve, ms));
}

async function playDash(currentPlayCounter) {
  startNotePlaying();
  await sleep(DASH_TIME);
  stopNotePlaying();
}

async function playDot(currentPlayCounter) {
  startNotePlaying();
  await sleep(DOT_TIME);
  stopNotePlaying();
}

document.getElementById(&#39;playDot&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  resetTiming();
  playDot();
});
document.getElementById(&#39;playDash&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  resetTiming();
  playDash();
});
&lt;/script&gt;
&lt;hr&gt;
&lt;h2 id=&#34;playing-morse-code&#34;&gt;Playing Morse code&lt;/h2&gt;
&lt;h3 id=&#34;morse-code-timing&#34;&gt;Morse code timing&lt;/h3&gt;
&lt;p&gt;Speaking of those time delays, let&amp;rsquo;s take a moment to look the timing of Morse code. The canonical ratios are as follows (&lt;a href=&#34;https://morsecode.world/international/timing.html&#34;&gt;from MorseCode.World&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The timing in Morse code is based around the length of one &amp;ldquo;dit&amp;rdquo; (or &amp;ldquo;dot&amp;rdquo; if you like). From the dit length we can derive the length of a &amp;ldquo;dah&amp;rdquo; (or &amp;ldquo;dash&amp;rdquo;) and the various pauses:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Dit: 1 unit&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Dah: 3 units&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Intra-character space (the gap between dits and dahs within a character): 1 unit&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Inter-character space (the gap between the characters of a word): 3 units&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Word space (the gap between two words): 7 units&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Or, in code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;var DOT_TIME = 60;  // If our unit is 60ms.
var DASH_TIME = DOT_TIME * 3;
var SYMBOL_BREAK = DOT_TIME;
var LETTER_BREAK = DOT_TIME * 3;
var WORD_BREAK = DOT_TIME * 7;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;playing-letters-words-and-sentences&#34;&gt;Playing letters, words, and sentences&lt;/h3&gt;
&lt;p&gt;Using our async and sleep tricks from earlier, we can start to build higher order functions to play our Morse code letters. For a given character, we&amp;rsquo;ll want to play each symbol with a symbol break in between. We can write a &lt;code&gt;playLetter&lt;/code&gt; function that takes in a letter&amp;rsquo;s Morse code representation (e.g. &lt;code&gt;&amp;quot;.-.&amp;quot;&lt;/code&gt; for &lt;code&gt;&amp;quot;r&amp;quot;&lt;/code&gt;) and plays it.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/**
 * letter is something like &amp;#39;---&amp;#39;
 */
async function playLetter(letter) {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  for (let i = 0; i &amp;lt; letter.length; i++) {
    if (letter[i] == &amp;#39;-&amp;#39;) {
      await playDash(currentPlayCounter);
    } else if (letter[i] == &amp;#39;.&amp;#39;) {
      await playDot(currentPlayCounter);
    }
    await sleep(SYMBOL_BREAK);
  }
}

playLetter(&amp;#39;.-.&amp;#39;);  // &amp;#34;r&amp;#34; is &amp;#34;.-.&amp;#34; in Morse code
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;button id=&#34;playLetter&#34;&gt;Play &amp;ldquo;r&amp;rdquo; in Morse code&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
function resetTiming() {
  DOT_TIME = 60;
  DASH_TIME = DOT_TIME * 3;
  SYMBOL_BREAK = DOT_TIME;
  LETTER_BREAK = DOT_TIME * 3;
  WORD_BREAK = DOT_TIME * 7;
}
var DOT_TIME = 60;
var DASH_TIME = DOT_TIME * 3;
var SYMBOL_BREAK = DOT_TIME;
var LETTER_BREAK = DOT_TIME * 3;
var WORD_BREAK = DOT_TIME * 7;
/**
 * letter is something like &#39;---&#39;
 */
async function playLetter(letter) {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  for (let i = 0; i &lt; letter.length; i++) {
    if (letter[i] == &#39;-&#39;) {
      await playDash();
    } else if (letter[i] == &#39;.&#39;) {
      await playDot();
    }
    await sleep(SYMBOL_BREAK);
  }
}
document.getElementById(&#39;playLetter&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  resetTiming();
  playLetter(&#39;.-.&#39;);
});
&lt;/script&gt;
&lt;p&gt;We can then build this up one level higher, into playing a word. Now, we&amp;rsquo;ll want to play each letter, then wait for the duration of the letter break:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Word is an array of letters as Morse code, like [&amp;#39;.&amp;#39;, &amp;#39;.-&amp;#39;, &amp;#39;-&amp;#39;]
async function playWord(word) {
  for (let i = 0; i &amp;lt; word.length; i++) {
    await playLetter(word[i]);
    await sleep(LETTER_BREAK);
  }
}

playWord([&amp;#39;.&amp;#39;, &amp;#39;.-&amp;#39;, &amp;#39;-&amp;#39;]);  // &amp;#34;eat&amp;#34; is &amp;#34;. .- -&amp;#34; in Morse code
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;button id=&#34;playAre&#34;&gt;Play &amp;ldquo;eat&amp;rdquo; in Morse code&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
// Word is an array of letters as Morse code, like [&#39;.&#39;, &#39;.-&#39;, &#39;-&#39;]
async function playWord(word) {
  for (let i = 0; i &lt; word.length; i++) {
    await playLetter(word[i]);
    await sleep(LETTER_BREAK);
  }
}
document.getElementById(&#39;playAre&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  resetTiming();
  playWord([&#39;.&#39;, &#39;.-&#39;, &#39;-&#39;]);
});
&lt;/script&gt;
&lt;p&gt;Finally, we can do one more higher order function to play a sentence, again reusing what we&amp;rsquo;ve already done. We&amp;rsquo;ll play each word, followed by a word break.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Sentence is an array of words.
// Ex. &amp;#34;dog is good&amp;#34; -&amp;gt; [[&amp;#39;-..&amp;#39;, &amp;#39;---&amp;#39;, &amp;#39;--.&amp;#39;], [&amp;#39;..&amp;#39;, &amp;#39;...&amp;#39;], [&amp;#39;--.&amp;#39;, &amp;#39;---&amp;#39;, &amp;#39;---&amp;#39;, &amp;#39;-..&amp;#39;]]
async function playSentence(sentence, currentPlayCounter) {
  for (let i = 0; i &amp;lt; sentence.length; i++) {
    await playWord(sentence[i], currentPlayCounter);
    await sleep(WORD_BREAK);
  }
}

playSentence([[&amp;#39;-..&amp;#39;, &amp;#39;---&amp;#39;, &amp;#39;--.&amp;#39;], [&amp;#39;..&amp;#39;, &amp;#39;...&amp;#39;], [&amp;#39;--.&amp;#39;, &amp;#39;---&amp;#39;, &amp;#39;---&amp;#39;, &amp;#39;-..&amp;#39;]]);  // &amp;#34;dog is good&amp;#34; in Morse code
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;button id=&#34;playDogIsGood&#34;&gt;Play &amp;ldquo;dog is good&amp;rdquo; in Morse code&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
async function playSentence(sentence, currentPlayCounter) {
  for (let i = 0; i &lt; sentence.length; i++) {
    await playWord(sentence[i], currentPlayCounter);
    await sleep(WORD_BREAK);
  }
}
document.getElementById(&#39;playDogIsGood&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  resetTiming();
  playSentence([[&#39;-..&#39;, &#39;---&#39;, &#39;--.&#39;], [&#39;..&#39;, &#39;...&#39;], [&#39;--.&#39;, &#39;---&#39;, &#39;---&#39;, &#39;-..&#39;]]);
});
&lt;/script&gt;
&lt;p&gt;Writing each word out in Morse code is pretty rough, though. We can write a translation layer to translate from a string (anything from a letter to a sentence) to the Morse code representation, starting with a map of letter to Morse:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const MORSE_MAP = {
  &amp;#39;a&amp;#39;: &amp;#39;.-&amp;#39;,
  &amp;#39;b&amp;#39;: &amp;#39;-...&amp;#39;,
  &amp;#39;c&amp;#39;: &amp;#39;-.-.&amp;#39;,
  &amp;#39;d&amp;#39;: &amp;#39;-..&amp;#39;,
  &amp;#39;e&amp;#39;: &amp;#39;.&amp;#39;,
  &amp;#39;f&amp;#39;: &amp;#39;..-.&amp;#39;,
  &amp;#39;g&amp;#39;: &amp;#39;--.&amp;#39;,
  &amp;#39;h&amp;#39;: &amp;#39;....&amp;#39;,
  &amp;#39;i&amp;#39;: &amp;#39;..&amp;#39;,
  &amp;#39;j&amp;#39;: &amp;#39;.---&amp;#39;,
  &amp;#39;k&amp;#39;: &amp;#39;-.-&amp;#39;,
  &amp;#39;l&amp;#39;: &amp;#39;.-..&amp;#39;,
  &amp;#39;m&amp;#39;: &amp;#39;--&amp;#39;,
  &amp;#39;n&amp;#39;: &amp;#39;-.&amp;#39;,
  &amp;#39;o&amp;#39;: &amp;#39;---&amp;#39;,
  &amp;#39;p&amp;#39;: &amp;#39;.--.&amp;#39;,
  &amp;#39;q&amp;#39;: &amp;#39;--.-&amp;#39;,
  &amp;#39;r&amp;#39;: &amp;#39;.-.&amp;#39;,
  &amp;#39;s&amp;#39;: &amp;#39;...&amp;#39;,
  &amp;#39;t&amp;#39;: &amp;#39;-&amp;#39;,
  &amp;#39;u&amp;#39;: &amp;#39;..-&amp;#39;,
  &amp;#39;v&amp;#39;: &amp;#39;...-&amp;#39;,
  &amp;#39;w&amp;#39;: &amp;#39;.--&amp;#39;,
  &amp;#39;x&amp;#39;: &amp;#39;-..-&amp;#39;,
  &amp;#39;y&amp;#39;: &amp;#39;-.--&amp;#39;,
  &amp;#39;z&amp;#39;: &amp;#39;--..&amp;#39;,
};
// asciiChar is something like &amp;#39;d&amp;#39;
// Assumes [a-z0-9]
function convertAsciiCharToMorse(asciiChar) {
  return MORSE_MAP[asciiChar];
}

// asciiWord is something like &amp;#39;dog&amp;#39;
// Assumes [a-z0-9]
function convertAsciiWordToMorse(asciiWord) {
  return asciiWord.split(&amp;#39;&amp;#39;).map(convertAsciiCharToMorse);
}

// asciiSentence is something like &amp;#39;dog is good&amp;#39;
// Assumes words are separated by spaces.
function convertAsciiSentenceToMorse(asciiSentence) {
  let splitSentence = asciiSentence.toLowerCase().split(&amp;#39; &amp;#39;);
  return splitSentence.map(convertAsciiWordToMorse);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then use these higher order functions to translate our strings first.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let targetString = &amp;#39;dog is good&amp;#39;;
playSentence(convertAsciiSentenceToMorse(targetString));
&lt;/code&gt;&lt;/pre&gt;&lt;script&gt;
const MORSE_MAP = {
  &#39;a&#39;: &#39;.-&#39;,
  &#39;b&#39;: &#39;-...&#39;,
  &#39;c&#39;: &#39;-.-.&#39;,
  &#39;d&#39;: &#39;-..&#39;,
  &#39;e&#39;: &#39;.&#39;,
  &#39;f&#39;: &#39;..-.&#39;,
  &#39;g&#39;: &#39;--.&#39;,
  &#39;h&#39;: &#39;....&#39;,
  &#39;i&#39;: &#39;..&#39;,
  &#39;j&#39;: &#39;.---&#39;,
  &#39;k&#39;: &#39;-.-&#39;,
  &#39;l&#39;: &#39;.-..&#39;,
  &#39;m&#39;: &#39;--&#39;,
  &#39;n&#39;: &#39;-.&#39;,
  &#39;o&#39;: &#39;---&#39;,
  &#39;p&#39;: &#39;.--.&#39;,
  &#39;q&#39;: &#39;--.-&#39;,
  &#39;r&#39;: &#39;.-.&#39;,
  &#39;s&#39;: &#39;...&#39;,
  &#39;t&#39;: &#39;-&#39;,
  &#39;u&#39;: &#39;..-&#39;,
  &#39;v&#39;: &#39;...-&#39;,
  &#39;w&#39;: &#39;.--&#39;,
  &#39;x&#39;: &#39;-..-&#39;,
  &#39;y&#39;: &#39;-.--&#39;,
  &#39;z&#39;: &#39;--..&#39;,
};
// asciiChar is something like &#39;d&#39;
// Assumes [a-z0-9]
function convertAsciiCharToMorse(asciiChar) {
  return MORSE_MAP[asciiChar];
}

// asciiWord is something like &#39;dog&#39;
// Assumes [a-z0-9]
function convertAsciiWordToMorse(asciiWord) {
  return asciiWord.split(&#39;&#39;).map(convertAsciiCharToMorse);
}

// asciiSentence is something like &#39;dog is good&#39;
// Assumes words are separated by spaces.
function convertAsciiSentenceToMorse(asciiSentence) {
  let splitSentence = asciiSentence.toLowerCase().split(&#39; &#39;);
  return splitSentence.map(convertAsciiWordToMorse);
}
&lt;/script&gt;
&lt;p&gt;&lt;button id=&#34;playHigherOrderDogIsGood&#34;&gt;Play &amp;ldquo;dog is good&amp;rdquo; from the string&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
document.getElementById(&#39;playHigherOrderDogIsGood&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  let targetString = &#39;dog is good&#39;;
  resetTiming();
  playSentence(convertAsciiSentenceToMorse(targetString));
});
&lt;/script&gt;
&lt;p&gt;With that, we&amp;rsquo;re able to play Morse code! We&amp;rsquo;ve built our playing up from playing a single tone for a duration to translating entire sentences and playing them.&lt;/p&gt;
&lt;h3 id=&#34;adjusting-the-speed&#34;&gt;Adjusting the speed&lt;/h3&gt;
&lt;p&gt;One of the things I wanted to do for these games was to make the speed adjustable, since it&amp;rsquo;s much easier to go slow when you&amp;rsquo;re first learning. This brought up an interesting question though: how should you slow down Morse code to learn?&lt;/p&gt;
&lt;p&gt;My naive approach was to just adjust the dot value and keep the ratios all the same. This would (I presumed) give you plenty of time to listen carefully and learn.&lt;/p&gt;
&lt;p&gt;Try out the following values with a very slow 300ms dot:&lt;/p&gt;
&lt;p&gt;&lt;button id=&#34;playDotLong&#34;&gt;Play 300ms dot&lt;/button&gt;
&lt;button id=&#34;playDashLong&#34;&gt;Play 900ms dash&lt;/button&gt;
&lt;button id=&#34;playEatLong&#34;&gt;Play &amp;rsquo;eat&amp;rsquo; with these values&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
document.getElementById(&#39;playDotLong&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  DOT_TIME = 300;
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  playDot();
});
document.getElementById(&#39;playDashLong&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  DOT_TIME = 300;
  DASH_TIME = DOT_TIME * 3;
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  playDash();
});
document.getElementById(&#39;playEatLong&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  DOT_TIME = 300;
  DASH_TIME = DOT_TIME * 3;
  SYMBOL_BREAK = DOT_TIME;
  LETTER_BREAK = DOT_TIME * 3;
  WORD_BREAK = DOT_TIME * 7;
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  let targetString = &#39;eat&#39;;
  playSentence(convertAsciiSentenceToMorse(targetString));
});
&lt;/script&gt;
&lt;p&gt;It turns out this isn&amp;rsquo;t actually helpful, since a large part of learning Morse code is being able to recognize and parse the sound of entire letters and words. If you slow everything down, you don&amp;rsquo;t build the familiarity with identifying the real sounds at the real speeds.  Instead, there&amp;rsquo;s a technique called the &lt;a href=&#34;https://www.google.com/search?q=Farnsworth+method&#34;&gt;Farnsworth method&lt;/a&gt;, where the individual letters are kept at the same speed, but the space between them varies. This gives you the benefit of hearing the letters at full speed with a longer break to process in between characters.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to the commenters on &lt;a href=&#34;https://news.ycombinator.com/item?id=30221869&#34;&gt;Hacker News&lt;/a&gt; for recommending this; I had never heard about it!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If we have some difficulty input, we can do something like the following to vary the spacing in between characters and words:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function updateSpeed(difficulty) {
  // Get the difficulty and update the constants.
  DOT_TIME = 60;
  DASH_TIME = DOT_TIME * 3;
  SYMBOL_BREAK = DOT_TIME;
  let letterBreakMultiplier;
  switch (difficulty) {
    case &amp;#39;easy&amp;#39;:
      letterBreakMultiplier = 24;
      break;
    case &amp;#39;medium&amp;#39;:
      letterBreakMultiplier = 12;
      break;
    case &amp;#39;hard&amp;#39;:
      letterBreakMultiplier = 6;
      break;
    default:
      letterBreakMultiplier = 3;
      break;
  }
  LETTER_BREAK = DOT_TIME * letterBreakMultiplier;
  WORD_BREAK = DOT_TIME * (letterBreakMultiplier * 2.5);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, try listening to &lt;code&gt;&amp;quot;eat&amp;quot;&lt;/code&gt; again, now with the Farnsworth method. Listen to how each letter is at full speed, but you have space to think in between!&lt;/p&gt;
&lt;p&gt;&lt;button id=&#34;playEatFarnsworth&#34;&gt;Play &amp;rsquo;eat&amp;rsquo; with the Farnsworth method&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
document.getElementById(&#39;playEatFarnsworth&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  DOT_TIME = 60;
  DASH_TIME = DOT_TIME * 3;
  SYMBOL_BREAK = DOT_TIME;
  let letterBreakMultiplier = 24;
  LETTER_BREAK = DOT_TIME * letterBreakMultiplier;
  WORD_BREAK = DOT_TIME * (letterBreakMultiplier * 2.5);
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  let targetString = &#39;eat&#39;;
  playSentence(convertAsciiSentenceToMorse(targetString));
});
&lt;/script&gt;
&lt;p&gt;With all of the playing and the timing figured out, we can now move to the games.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-listening-game&#34;&gt;The Listening Game&lt;/h2&gt;
&lt;p&gt;Now that we have the ability to play an arbitrary phrase, ranging from one letter to complete sentences, we can use that to make the first game, the listening game. This game works as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get a random target phrase depending on the difficulty&lt;/li&gt;
&lt;li&gt;Play the phrase&lt;/li&gt;
&lt;li&gt;Wait for the user to type it in
&lt;ul&gt;
&lt;li&gt;If they get it, tell them and give option to play again&lt;/li&gt;
&lt;li&gt;If not, play the sound again&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;First, let&amp;rsquo;s look at how we&amp;rsquo;d get a random target. Let&amp;rsquo;s say we have an input with a few different radio buttons:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function getTarget(difficulty) {
  // Get the difficulty and assign a random target.
  let target;
  switch (difficulty) {
    case &amp;#39;easy&amp;#39;:
      target = getRandomLetter();
      break;
    case &amp;#39;medium&amp;#39;:
      target = getRandomWord();
      break;
    case &amp;#39;hard&amp;#39;:
      target = getRandomEasyWords();
      break;
    default:
      target = getRandomWords();
      break;
  }
  return target;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s not too bad! We just have to implement those child functions:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Note: in the game, EASY_WORDS and ALL_WORDS are long arrays of strings.
// Here are a Shown here for example.
const EASY_WORDS = [&amp;#39;cat&amp;#39;, &amp;#39;ate&amp;#39;, &amp;#39;dog&amp;#39;];
const ALL_WORDS = [&amp;#39;cat&amp;#39;, &amp;#39;ate&amp;#39;, &amp;#39;dog&amp;#39;, &amp;#39;interest&amp;#39;, &amp;#39;complexity&amp;#39;];

function getRandomLetter() {
  const randomIndex = Math.floor(Math.random() * 26);
  return Object.keys(MORSE_MAP)[randomIndex];
}

function getRandomWord() {
  const randomIndex = Math.floor(Math.random() * EASY_WORDS.length);
  return EASY_WORDS[randomIndex];
}

function getRandomEasyWords() {
  const totalLength = Math.floor(Math.random() * 3) + 2;
  let finalSentence = &amp;#39;&amp;#39;
  for (let i = 0; i &amp;lt; totalLength; i++) {
    finalSentence += EASY_WORDS[Math.floor(Math.random() * EASY_WORDS.length)];
    if (i &amp;lt; totalLength - 1) {
      finalSentence += &amp;#39; &amp;#39;;
    }
  }
  return finalSentence;
}

function getRandomWords() {
  const totalLength = Math.floor(Math.random() * 4) + 2;
  let finalSentence = &amp;#39;&amp;#39;
  for (let i = 0; i &amp;lt; totalLength; i++) {
    finalSentence += ALL_WORDS[Math.floor(Math.random() * ALL_WORDS.length)];
    if (i &amp;lt; totalLength - 1) {
      finalSentence += &amp;#39; &amp;#39;;
    }
  }
  return finalSentence;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With these in hand, we can write the basic game functions:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// The listening game
let target = &amp;#39;&amp;#39;;

function startGame() {
  // Initialize audio context if it isn&amp;#39;t already
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  // Get a random target, assume we&amp;#39;re on medium mode
  target = getTarget(&amp;#39;medium&amp;#39;);
  // Play the target phrase
  playTarget();
}

function playTarget() {
  playSentence(convertAsciiSentenceToMorse(target));
}

function guess(enteredGuess) {
  if (enteredWord == target) {
    console.log(&amp;#39;Correct! call `startGame()` to play again.&amp;#39;);
  } else {
    console.log(&amp;#39;Not quite! Try again.&amp;#39;);
    playTarget();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;script&gt;
function getTarget(difficulty) {
  // Get the difficulty and assign a random target.
  let target;
  switch (difficulty) {
    case &#39;easy&#39;:
      target = getRandomLetter();
      break;
    case &#39;medium&#39;:
      target = getRandomWord();
      break;
    case &#39;hard&#39;:
      target = getRandomEasyWords();
      break;
    default:
      target = getRandomWords();
      break;
  }
  return target;
}
// Note: in the game, EASY_WORDS and ALL_WORDS are long arrays of strings.
// Here are a Shown here for example.
const EASY_WORDS = [&#39;cat&#39;, &#39;ate&#39;, &#39;dog&#39;];
const ALL_WORDS = [&#39;cat&#39;, &#39;ate&#39;, &#39;dog&#39;, &#39;interest&#39;, &#39;complexity&#39;];

function getRandomLetter() {
  const randomIndex = Math.floor(Math.random() * 26);
  return Object.keys(MORSE_MAP)[randomIndex];
}

function getRandomWord() {
  const randomIndex = Math.floor(Math.random() * EASY_WORDS.length);
  return EASY_WORDS[randomIndex];
}

function getRandomEasyWords() {
  const totalLength = Math.floor(Math.random() * 3) + 2;
  let finalSentence = &#39;&#39;
  for (let i = 0; i &lt; totalLength; i++) {
    finalSentence += EASY_WORDS[Math.floor(Math.random() * EASY_WORDS.length)];
    if (i &lt; totalLength - 1) {
      finalSentence += &#39; &#39;;
    }
  }
  return finalSentence;
}

function getRandomWords() {
  const totalLength = Math.floor(Math.random() * 4) + 2;
  let finalSentence = &#39;&#39;
  for (let i = 0; i &lt; totalLength; i++) {
    finalSentence += ALL_WORDS[Math.floor(Math.random() * ALL_WORDS.length)];
    if (i &lt; totalLength - 1) {
      finalSentence += &#39; &#39;;
    }
  }
  return finalSentence;
}
// The listening game
let target = &#39;&#39;;

function startListeningGame() {
  DOT_TIME = 60;
  DASH_TIME = DOT_TIME * 3;
  SYMBOL_BREAK = DOT_TIME;
  let letterBreakMultiplier = 24;
  LETTER_BREAK = DOT_TIME * letterBreakMultiplier;
  WORD_BREAK = DOT_TIME * (letterBreakMultiplier * 2.5);
  // Initialize audio context if it isn&#39;t already
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  // Get a random target, assume we&#39;re on medium mode
  target = getTarget(&#39;medium&#39;);
  // Play the target phrase
  playTarget();
}

function replay() {
  playTarget();
}

function playTarget() {
  playSentence(convertAsciiSentenceToMorse(target));
}

function guess(enteredWord) {
  if (enteredWord == target) {
    console.log(&#39;Correct! call `startListeningGame()` to play again.&#39;);
  } else {
    console.log(&#39;Not quite! Try again.&#39;);
    playTarget();
  }
}
&lt;/script&gt;
&lt;p&gt;That&amp;rsquo;s the main functionality right there! You can actually play this minimal version in your console on this page:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;listening-game-console.png&#34; alt=&#34;Screenshot of playing the listening game in the console&#34;&gt;&lt;/p&gt;
&lt;p&gt;All that&amp;rsquo;s left to do with this is to clean it up and hook it up to the user input (buttons and keyboard shortcuts) — since that&amp;rsquo;s mostly event listeners, I&amp;rsquo;m omitting it here, but you can see the full source &lt;a href=&#34;https://alexanderell.is/posts/morse-code/morse-code.js&#34;&gt;here&lt;/a&gt;; look for the &lt;code&gt;ListeningGame&lt;/code&gt; class.&lt;/p&gt;
&lt;h4 id=&#34;replaying-the-target&#34;&gt;Replaying the target&lt;/h4&gt;
&lt;p&gt;One subtle thing that came up with this game is how to allow the user to replay the target phrase, and mostly what would happen if they tried to replay it before the playthrough was finished.&lt;/p&gt;
&lt;p&gt;I initially tried to manage this with a global &lt;code&gt;STOPPED&lt;/code&gt; variable and a few checks; if the &lt;code&gt;playSentence&lt;/code&gt; function saw that everything was stopped, it wouldn&amp;rsquo;t try to play any more. This quickly ran into problems related to the different sleeps I have in there: since I set it up so that a new playthrough would set &lt;code&gt;STOPPED&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;, this meant it could be reset while the earlier &lt;code&gt;playSentence&lt;/code&gt; was still sleeping, leading to the original playthrough still continuing. This meant that two playthroughs would play at the same time, which quickly led to a terribly messing sounding audio.&lt;/p&gt;
&lt;p&gt;I ended up fixing this by introducing a monotonically increasing playthrough counter global variable and introducing an invariant that only the most recent playthrough can be played. By incrementing this variable when a playthrough is initiated and passing the value to the child play calls, each child function can perform a check if its passed playthrough ID still matches the global current playthrough ID; if not, it means that there&amp;rsquo;s a newer playthrough and the current one should be discarded.&lt;/p&gt;
&lt;p&gt;This looked something like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Global strictly increasing play counter to avoid multiple replays at the same time.
var playCounter = 0;

async function playDash(currentPlayCounter) {
  if (currentPlayCounter != playCounter) { return; } // New line to short circuit if out of date
  ... rest of function
}
async function playDot(currentPlayCounter) {
  if (currentPlayCounter != playCounter) { return; } // New line to short circuit if out of date
  ... rest of function
}
async function playLetter(letter, currentPlayCounter) {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  for (let i = 0; i &amp;lt; letter.length; i++) {
    if (currentPlayCounter != playCounter) { return; }
    ... rest of function
  }
}
async function playWord(word, currentPlayCounter) {
  for (let i = 0; i &amp;lt; word.length; i++) {
    if (currentPlayCounter != playCounter) { return; }
    ... rest of function
  }
}
async function playSentence(sentence, currentPlayCounter) {
  if (currentPlayCounter != playCounter) { return; }
  ... rest of function
}
playTarget() {
  playCounter += 1;
  playSentence(convertAsciiSentenceToMorse(this.target), playCounter);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This way, if playTarget was called again, the new playthrough would supersede the old one, and the old one would stop. Not the most elegant, but it removed the duplicate plays!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Thanks to sirmarksalot on Hacker News for pointing out this bug!)&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-speaking-game&#34;&gt;The &amp;ldquo;Speaking&amp;rdquo; game&lt;/h2&gt;
&lt;p&gt;The second game was a little trickier to write, as it&amp;rsquo;s much more dynamic than the first one. It requires the player to input the Morse code representation of a given target (again, depending on the difficulty, ranging from a single letter to a whole sentence).&lt;/p&gt;
&lt;p&gt;You can imagine the rough steps are like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get a random target phrase depending on the difficulty&lt;/li&gt;
&lt;li&gt;Listen for user inputs
&lt;ul&gt;
&lt;li&gt;Translate user input into signals and pauses&lt;/li&gt;
&lt;li&gt;Translate signals and pauses into letters and words&lt;/li&gt;
&lt;li&gt;Check if the user input the target correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Much more of a dynamic problem!&lt;/p&gt;
&lt;h3 id=&#34;parsing-the-incoming-signal-into-morse-code&#34;&gt;Parsing the incoming signal into Morse code&lt;/h3&gt;
&lt;p&gt;For an initial consideration, we can imagine just the part of parsing user signals. Since we&amp;rsquo;d be constructing letters from symbols, we can approach it like other &amp;ldquo;build up as we go&amp;rdquo; problems and use a short stack to keep track of what we&amp;rsquo;ve seen so far. Let&amp;rsquo;s imagine it going left to right, since that&amp;rsquo;s how we&amp;rsquo;d read it in English, and let&amp;rsquo;s imagine a user inputting &lt;code&gt;&amp;quot;r&amp;quot;&lt;/code&gt;, which in Morse code is &lt;code&gt;&amp;quot;.-.&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;symbol-stack.png&#34; style=&#34;max-height: 400px&#34; alt=&#34;Stack growing by symbol, first with just &#39;.&#39;, then &#39;.-&#39;, then &#39;.-.&#39;&#34;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;At some point, we&amp;rsquo;ll process the symbols on the stack (more on that later) into a letter. We&amp;rsquo;ll want to build the letters up over time, so we can imagine another stack of letters:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;letter-stack.png&#34; style=&#34;max-height: 400px&#34; alt=&#34;Stack growing by letter, first with just &#39;r&#39;, then &#39;ru&#39;, then &#39;run&#39;&#34;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;If we&amp;rsquo;re dealing with sentences, you could imagine there being an additional stack for the words, too.&lt;/p&gt;
&lt;p&gt;Like any interesting stack, the real question is when to read from it. This is where the pauses and silences come into play. Earlier we talked about the ratios for the pauses (the &lt;code&gt;SYMBOL_BREAK&lt;/code&gt;, &lt;code&gt;LETTER_BREAK&lt;/code&gt;, and &lt;code&gt;WORD_BREAK&lt;/code&gt; from earlier). We can abstract away the actual input and imagine a decision tree like the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;decision-tree.png&#34; alt=&#34;Decision tree of parsing silence and signal&#34;&gt;&lt;/p&gt;
&lt;p&gt;That gives us four events to handle: &lt;code&gt;handleDot&lt;/code&gt;, &lt;code&gt;handleDash&lt;/code&gt;, &lt;code&gt;handleLetterBreak&lt;/code&gt;, and &lt;code&gt;handleWordBreak&lt;/code&gt;.  With our stacks from earlier, those could do the following, where we parse the symbol stack in &lt;code&gt;handleLetterBreak&lt;/code&gt; and the letter stack in &lt;code&gt;handleWordBreak&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let symbolStack = [];
let letterStack = [];
let wordStack = [];

function handleDot() {
  symbolStack.push(&amp;#39;.&amp;#39;);
}

function handleDash() {
  symbolStack.push(&amp;#39;-&amp;#39;);
}

function handleLetterBreak() {
  const letterAsMorse = symbolStack.join(&amp;#39;&amp;#39;);
  // Assume we have a mapping of Morse:letter,
  // the reverse of the earlier MORSE_MAP
  const letter = lookUpMorse(letterAsMorse);
  letterStack.push(letter);
  // Clear the symbol stack.
  symbolStack = [];
}

function handleWordBreak() {
  const word = letterStack.join(&amp;#39;&amp;#39;);
  wordStack.push(word);
  // Clear the word stack.
  wordStack = [];
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This looks pretty good. We&amp;rsquo;ll be incrementally building up our inputs as we see them, from symbol all the way up to words, pushing to stacks and parsing them when appropriate. Now, how do we translate user input into these chunks?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by imagining our signal as an electrical signal (much like our oscillation from before) as a square wave, to make it easy to see. Let&amp;rsquo;s say we have &lt;code&gt;&amp;quot;r&amp;quot;&lt;/code&gt; as an input.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;square-wave.png&#34; alt=&#34;Visualizing Morse code input as a square wave&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;button id=&#34;playLetter2&#34;&gt;Press to hear the Morse code for &amp;ldquo;r&amp;rdquo;&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
document.getElementById(&#39;playLetter2&#39;).addEventListener(&#39;click&#39;, () =&gt; {
  if (!audioContextInitialized) {
    initializeAudioContext();
  }
  resetTiming();
  playLetter(&#39;.-.&#39;);
});
&lt;/script&gt;
&lt;p&gt;Parsing the high signal seems pretty easy, as we can just look at the time when the signal starts and the time when the signal stops. We can say it&amp;rsquo;s a dot if it&amp;rsquo;s shorter than a &lt;code&gt;DASH_TIME&lt;/code&gt;, and if it&amp;rsquo;s longer, then it&amp;rsquo;s clearly a dash.Since humans aren&amp;rsquo;t perfect, we can be a little generous about the time.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let signalStartTime = 0;

function handleSignalStart() {
  signalStartTime = Date.now();
}

function handleSignalEnd() {
  // How long was this signal?
  let timeDelta = Date.now() - signalStartTime;
  if (timeDelta &amp;lt; DASH_TIME) {
    handleDot();
  } else {
    handleDash();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we tie these in to the points where the signal starts and stops, we can parse input into dots and dashes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;parsing-signal.png&#34; alt=&#34;Parsing the signal&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now, how can we parse the silences? One initial approach could be to look backwards whenever a signal starts. Silence is the absence of signal, and every time a signal starts, we can look to see how long it has been since the last signal ended. This is the pause between signals, and we can parse it appropriately. We can even ignore the first silent section, since we don&amp;rsquo;t want a leading space before any input.&lt;/p&gt;
&lt;p&gt;To add to the previous code, we now need to also keep track of the timestamp when the signal ended.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let signalStartTime = 0;
let signalEndTime = 0;

function handleSignalStart() {
  let currentTime = Date.now();
  // Keep track of signal start time as before.
  signalStartTime = currentTime;
  // Now, calculate how long that silence was.
  let silenceTimeDelta = currentTime - signalEndTime;
  if (signalEndTime == 0) {
    // This is the first silence and we can ignore it.
  } else if (silenceTimeDelta &amp;lt; LETTER_BREAK) {
    // This is just a symbol break and we can ignore it.
  } else if (silenceTimeDelta &amp;lt; WORD_BREAK) {
    // Anything more than a letter break and less than a word break counts
    // as a letter break.
    handleLetterBreak();
  } else {
    // We have a word break!
    handleWordBreak();
  }
}

function handleSignalEnd() {
  let currentTime = Date.now();
  // How long was this signal? Calculate delta as before
  let timeDelta = currentTime - signalStartTime;
  if (timeDelta &amp;lt; DASH_TIME) {
    handleDot();
  } else {
    handleDash();
  }
  // We also need to mark the timestamp that this signal ended.
  signalEndTime = currentTime;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, our parsing for &lt;code&gt;&amp;quot;r&amp;quot;&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;look-back-silence.png&#34; alt=&#34;Parsing the silence by looking back&#34;&gt;&lt;/p&gt;
&lt;p&gt;Close, but there&amp;rsquo;s a problem. If we&amp;rsquo;re relying on a new signal to know when the current silence is done, what if we don&amp;rsquo;t get a new signal? If the player has entered the last symbol, we should be able to automatically know that enough time has passed for a letter break. Additionally, if we&amp;rsquo;re dynamically processing the user&amp;rsquo;s input, we should be able to show it to them as it&amp;rsquo;s parsed. If we have to wait for the next signal to know that the current symbol is done, there would be a weird effect where you&amp;rsquo;d only see the current letter once you start the signal for the next letter.&lt;/p&gt;
&lt;p&gt;For example, what if the player had to input &lt;code&gt;&amp;quot;a&amp;quot;&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;look-back-silence-bad.png&#34; alt=&#34;Parsing the silence by looking back, but we never know that the letter is done!&#34;&gt;&lt;/p&gt;
&lt;p&gt;We need a way to &lt;em&gt;proactively&lt;/em&gt; parse the input when enough time has passed. Instead of the backwards-looking approach above, we can instead use a forward looking approach. When we see a signal end, we can start two timers: one the for time period of a letter break, and one for the time period of a word break. When these timers go off, if we haven&amp;rsquo;t seen a new signal, we&amp;rsquo;ll know that we can handle the letter breaks and word breaks appropriately. If we do see a signal, we can just cancel the timers. This also allows us to show the parsed input to the user as soon as it has been parsed, which feels much more responsive.&lt;/p&gt;
&lt;p&gt;Now, when a user inputs the word &lt;code&gt;&amp;quot;at&amp;quot;&lt;/code&gt;, we can proactively handle the silence.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;look-forward-silence.png&#34; alt=&#34;Parsing the silence looking forward with timers&#34;&gt;&lt;/p&gt;
&lt;p&gt;To do this in the code, we can use a combination of creating timeouts and clearing them as we need to.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let signalStartTime = 0;

// Timeout IDs we&amp;#39;ll keep around so we can clear them when needed.
let letterTimeout = null;
let wordTimeout = null;

function handleSignalStart() {
  signalStartTime = Date.now();
  // We also want to clear any pending silence timeouts.
  clearTimeout(letterTimeout);
  clearTimeout(spaceTimeout);
}

function handleSignalEnd() {
  // How long was this signal?
  let timeDelta = Date.now() - signalStartTime;
  if (timeDelta &amp;lt; DASH_TIME) {
    handleDot();
  } else {
    handleDash();
  }
  // Start the timeouts
  letterTimeout = setTimeout(handleLetterBreak, LETTER_BREAK);
  wordTimeout = setTimeout(handleWordbreak, WORD_BREAK);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now that we have this, we can put it all together:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;putting-it-all-together.png&#34; alt=&#34;Parsing silence and signal all together&#34;&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s really the hard part.&lt;/p&gt;
&lt;h3 id=&#34;hooking-it-up-to-user-input&#34;&gt;Hooking it up to user input&lt;/h3&gt;
&lt;p&gt;To make this feel dynamic, we can hook up the &lt;code&gt;handleSignalStart&lt;/code&gt; function to run on &lt;code&gt;keyDown&lt;/code&gt; for the spacebar and &lt;code&gt;mousedown&lt;/code&gt;/&lt;code&gt;touchstart&lt;/code&gt; on the Tap button and the &lt;code&gt;handleSignalEnd&lt;/code&gt; function to run on &lt;code&gt;keyUp&lt;/code&gt; for the spacebar and &lt;code&gt;mouseup&lt;/code&gt;/&lt;code&gt;touchend&lt;/code&gt; on the Tap button.&lt;/p&gt;
&lt;p&gt;To make it feel real, we want to play a tone when the user is pressing down on those inputs. Since we did all of the tone playing before, all we have to do is add a simple call to &lt;code&gt;startNotePlaying&lt;/code&gt; in &lt;code&gt;handleSignalStart&lt;/code&gt; and the corresponding &lt;code&gt;stopNotePlaying&lt;/code&gt; in &lt;code&gt;handleSignalEnd&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The last step is adding a similar flow to the first game, where we generate a target, listen and parse input, and check if it matches. All that&amp;rsquo;s left to do with this is to clean it up, add a little more functionality for clearing, and hook it up to the user input (buttons and keyboard shortcuts) — since that&amp;rsquo;s mostly event listeners, I&amp;rsquo;m again omitting it here, but you can see the full source &lt;a href=&#34;https://alexanderell.is/posts/morse-code/morse-code.js&#34;&gt;here&lt;/a&gt;; look for the &lt;code&gt;InputGame&lt;/code&gt; class.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;finishing-thoughts&#34;&gt;Finishing thoughts&lt;/h2&gt;
&lt;p&gt;I also cleaned up the games and put them in classes to keep things a little more cleanly separated. I also spent some time on some nice-to-haves, like auto-focusing on the text input when the first game starts and keyboard shortcuts to clear/reset.&lt;/p&gt;
&lt;p&gt;You can play the games &lt;a href=&#34;posts/morse-code&#34;&gt;here&lt;/a&gt; and see the full source code &lt;a href=&#34;https://alexanderell.is/posts/morse-code/morse-code.js&#34;&gt;here&lt;/a&gt; and &lt;a href=&#34;https://alexanderell.is/posts/morse-code/constants.js&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Feel free to reach out if you have any questions about this, and happy coding!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Little games to play with Morse Code in your browser</title>
      <link>https://alexanderell.is/posts/morse-code/</link>
      <pubDate>Fri, 04 Feb 2022 22:32:54 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/morse-code/</guid>
      <description>&lt;p&gt;&lt;em&gt;Be sure to also check out my other Morse code game, &lt;a href=&#34;https://alexanderell.is/posts/morse-codle&#34;&gt;Morse Codle&lt;/a&gt;, a daily Morse code Wordle puzzle.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is a pair of games for playing with Morse Code. Since the games play the signals as sound, I recommend headphones (wired if possible for the latency) and a low volume. Game options are available towards the bottom.&lt;/p&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;styles.css&#34;&gt;
&lt;noscript&gt;
  Unfortunately, JavaScript is needed to run these games. The only JS running on my site is for the games (no tracking, etc) if you want to enable it. The good news is that if you&#39;re disabling JavaScript, you probably already know some Morse Code :)
&lt;/noscript&gt;
&lt;h1 id=&#34;morse-code-listening-game&#34;&gt;Morse Code Listening Game&lt;/h1&gt;
&lt;p&gt;In this game, it will play a word in Morse Code, and you have to type in the word.&lt;/p&gt;
&lt;br&gt;
&lt;div class=&#34;game-container&#34;&gt;
  &lt;button id=&#34;playListeningGame&#34; class=&#34;start-button&#34;&gt;Start&lt;/button&gt;
  &lt;br&gt;
  &lt;p id=&#34;status&#34; class=&#34;display-message&#34;&gt;Press Start to begin.&lt;/p&gt;
  &lt;input type=&#34;text&#34; id=&#34;wordInput&#34; class=&#34;input&#34; placeholder=&#34;Enter message&#34; autocomplete=&#34;off&#34; disabled&gt;
  &lt;br&gt;
  &lt;div class=&#34;button-container&#34;&gt;
    &lt;button id=&#34;submitButton&#34; class=&#34;interaction-button&#34; disabled&gt;Enter&lt;/button&gt;
    &lt;button id=&#34;resetButton&#34; class=&#34;interaction-button&#34; disabled&gt;Replay&lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;details&gt;
  &lt;summary&gt;How to play the listening game&lt;/summary&gt;
  &lt;br&gt;
&lt;p&gt;As you hear the Morse Code, enter the letters (or words!) that you hear. Press Enter (either the button or on your keyboard) to see if you got it, and press Replay (or Control on your keyboard) to hear it again.&lt;/p&gt;
&lt;p&gt;For doing Morse to letter lookups, I find it helpful to look at a tree-shaped diagram, where you can follow from one node to the next based on whether it&amp;rsquo;s a dit or a dah.
&lt;a href=&#34;https://upload.wikimedia.org/wikipedia/commons/1/19/Morse-code-tree.svg&#34; target=&#34;blank_&#34;&gt;
&lt;img src=&#34;https://upload.wikimedia.org/wikipedia/commons/1/19/Morse-code-tree.svg&#34; alt=&#34;Binary Tree-shaped diagram of letters and their corresponding Morse Code representation.&#34;&gt;
&lt;/a&gt;
&lt;a href=&#34;https://commons.wikimedia.org/wiki/File:Morse-code-tree.svg&#34;&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;/details&gt;
&lt;br&gt;
&lt;h1 id=&#34;morse-code-speaking-game&#34;&gt;Morse Code &amp;ldquo;Speaking&amp;rdquo; Game&lt;/h1&gt;
&lt;p&gt;For the speaking game, it will show a word, and you&amp;rsquo;ll have to enter it in Morse Code.&lt;/p&gt;
&lt;div class=&#34;game-container&#34;&gt;
  &lt;button id=&#34;playInputGame&#34; class=&#34;start-button&#34;&gt;Start&lt;/button&gt;
  &lt;p id=&#34;inputStatus&#34; class=&#34;display-message&#34;&gt;&lt;/p&gt;
  &lt;p id=&#34;targetDisplay&#34; class=&#34;display-message&#34;&gt;Press Start to begin.&lt;/p&gt;
  &lt;p id=&#34;inputDisplay&#34; class=&#34;display-message morse-input&#34;&gt;&lt;/p&gt;
  &lt;div class=&#34;button-container&#34;&gt;
  &lt;button id=&#34;signalButton&#34; class=&#34;interaction-button&#34; disabled&gt;Tap&lt;/button&gt;
  &lt;button id=&#34;startOverButton&#34; class=&#34;interaction-button&#34; disabled&gt;Try Again&lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;details&gt;
  &lt;summary&gt;How to play the speaking game&lt;/summary&gt;
  &lt;p&gt;Press start to reveal the target. Enter Morse Code with your spacebar or the Tap button. Press Control or the Try Again button to clear your input and start over.&lt;/p&gt;
&lt;p&gt;The game will assume you&amp;rsquo;re done with a letter after the letter gap interval (see options below), and it will automatically insert a space for you after the word interval; be careful you don&amp;rsquo;t take too long in between dits!&lt;/p&gt;
&lt;p&gt;For doing letter to Morse lookups, I find it helpful to look at an alphabetical list with the matching representation. This makes looking up a letter faster.
&lt;a href=&#34;https://upload.wikimedia.org/wikipedia/commons/1/19/Morse-code-tree.svg&#34; target=&#34;blank_&#34;&gt;
&lt;img src=&#34;https://upload.wikimedia.org/wikipedia/commons/b/b5/International_Morse_Code.svg&#34; alt=&#34;Alphabetical list of letters and their corresponding Morse Code representation&#34;&gt;
&lt;/a&gt;
&lt;a href=&#34;https://en.wikipedia.org/wiki/File:International_Morse_Code.svg&#34;&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;/details&gt;
&lt;br&gt;
&lt;h2 id=&#34;game-options&#34;&gt;Game options&lt;/h2&gt;
&lt;p&gt;After changing settings, you have to stop and restart the above games.&lt;/p&gt;
&lt;h3 id=&#34;difficulty&#34;&gt;Difficulty&lt;/h3&gt;
&lt;legend&gt;Please select your preferred difficulty:&lt;/legend&gt;
&lt;div&gt;
  &lt;input type=&#34;radio&#34; id=&#34;difficultyEasy&#34;
    name=&#34;difficulty&#34; value=&#34;easy&#34; checked&gt;
  &lt;label for=&#34;difficultyEasy&#34;&gt;Easy (letters)&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;difficultyMedium&#34; name=&#34;difficulty&#34; value=&#34;medium&#34;&gt;
  &lt;label for=&#34;difficultyMedium&#34;&gt;Medium (short words)&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;difficultyHard&#34; name=&#34;difficulty&#34; value=&#34;hard&#34;&gt;
  &lt;label for=&#34;difficultyHard&#34;&gt;Hard (multiple short words)&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;difficultyVeryHard&#34; name=&#34;difficulty&#34; value=&#34;really-hard&#34;&gt;
  &lt;label for=&#34;difficultyVeryHard&#34;&gt;Very Hard (multiple words)&lt;/label&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h3 id=&#34;speed&#34;&gt;Speed&lt;/h3&gt;
&lt;legend&gt;Please select your preferred speed:&lt;/legend&gt;
&lt;div&gt;
  &lt;input type=&#34;radio&#34; id=&#34;speedEasy&#34;
    name=&#34;speed&#34; value=&#34;easy&#34; checked&gt;
  &lt;label for=&#34;speedEasy&#34;&gt;Slow&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;speedMedium&#34; name=&#34;speed&#34; value=&#34;medium&#34; &gt;
  &lt;label for=&#34;speedMedium&#34;&gt;Medium&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;speedHard&#34; name=&#34;speed&#34; value=&#34;hard&#34;&gt;
  &lt;label for=&#34;speedHard&#34;&gt;Fast&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;speedHarder&#34; name=&#34;speed&#34; value=&#34;harder&#34;&gt;
  &lt;label for=&#34;speedHarder&#34;&gt;Faster&lt;/label&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;legend&gt;Use the Farnsworth method?&lt;/legend&gt;
&lt;div&gt;
  &lt;input type=&#34;radio&#34; id=&#34;yesFarnsworth&#34; name=&#34;farnsworth&#34; value=&#34;yes&#34; checked&gt;
  &lt;label for=&#34;yesFarnsworth&#34;&gt;Yes&lt;/label&gt;
  &lt;br&gt;
  &lt;input type=&#34;radio&#34; id=&#34;noFarnsworth&#34;
    name=&#34;farnsworth&#34; value=&#34;no&#34;&gt;
  &lt;label for=&#34;noFarnsworth&#34;&gt;No&lt;/label&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;p&gt;The speeds match the following from Wikipedia:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The duration of a dah is three times the duration of a dit. Each dit or dah within an encoded character is followed by a period of signal absence, called a space, equal to the dit duration. The letters of a word are separated by a space of duration equal to three dits, and words are separated by a space equal to seven dits.&lt;/em&gt;&lt;/p&gt;
&lt;div id=&#34;speed-table&#34;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Dit (ms)&lt;/th&gt;
&lt;th&gt;Dash (ms)&lt;/th&gt;
&lt;th&gt;Letter gap (ms)&lt;/th&gt;
&lt;th&gt;Word gap (ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;td&gt;2100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;1400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Very Fast&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;But, that being said, the &lt;a href=&#34;https://www.google.com/search?q=Farnsworth+method&#34;&gt;Farnsworth method&lt;/a&gt; varies the intervals between the letters and the word gap instead of varying the length of the dits, dashes, or character gaps. It is more helpful for learning the sounds of each letter at a real speed you would hear it.&lt;/p&gt;
&lt;p&gt;All times are in milliseconds. I recommend starting with slow and giving yourself plenty of time for the dahs, then increasing the speed as you go!&lt;/p&gt;
&lt;br&gt;
&lt;h2 id=&#34;issuesquestions&#34;&gt;Issues/questions&lt;/h2&gt;
&lt;h4 id=&#34;something-went-wrong&#34;&gt;&lt;em&gt;Something went wrong?&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;You may need to refresh — it&amp;rsquo;s a little rough around the edges.&lt;/p&gt;
&lt;h4 id=&#34;something-went-wrong-on-ios&#34;&gt;&lt;em&gt;Something went wrong on iOS?&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;First, make sure your phone is unsilenced (turns out a silenced phone blocks the web audio APIs). You also may need to tap Start and Stop a few times, refresh, or start with the &amp;ldquo;speaking&amp;rdquo; game. I ran into an issue with initializing the AudioContext, and although I think I got it right, I&amp;rsquo;m sorry if it&amp;rsquo;s giving you trouble.&lt;/p&gt;
&lt;h4 id=&#34;why-is-it-so-quick-with-the-spaces-when-listening&#34;&gt;&lt;em&gt;Why is it so quick with the spaces when listening?&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;Sorry, it was easiest to be super strict about the timing when making it. Try slowing the speed down or practicing to go faster!&lt;/p&gt;
&lt;h4 id=&#34;how-does-it-work&#34;&gt;&lt;em&gt;How does it work?&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;You can read about it &lt;a href=&#34;https://alexanderell.is/posts/writing-morse-code-games&#34;&gt;here&lt;/a&gt;, and you can see the source code &lt;a href=&#34;morse-code.js&#34;&gt;here&lt;/a&gt; and &lt;a href=&#34;constants.js&#34;&gt;here&lt;/a&gt;, though it&amp;rsquo;s very unpolished.&lt;/p&gt;
&lt;h4 id=&#34;any-recommendations-for-learning-morse-code-for-real&#34;&gt;&lt;em&gt;Any recommendations for learning Morse Code for real?&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;m far from an expert, but so far I&amp;rsquo;ve seen a few recommendations (thanks to curiousfab and SaberTail on HN):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://morsecode.world/international/trainer/trainer.html&#34;&gt;https://morsecode.world/international/trainer/trainer.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lcwo.net/&#34;&gt;https://lcwo.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.elkins.org/&#34;&gt;http://www.elkins.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;script src=&#34;constants.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;morse-code.js&#34;&gt;&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>Writing a toy traceroute from scratch</title>
      <link>https://alexanderell.is/posts/toy-traceroute/</link>
      <pubDate>Sun, 30 Jan 2022 10:59:16 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/toy-traceroute/</guid>
      <description>&lt;p&gt;&lt;code&gt;traceroute&lt;/code&gt; is a tool you can use to trace the route of packets from your computer to another computer. It lets you see each step that your packets take along the way.&lt;/p&gt;
&lt;p&gt;For example, the following is the result of running &lt;code&gt;traceroute&lt;/code&gt; from my computer in Massachusetts to &amp;ldquo;example.com&amp;rdquo;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;traceroute.png&#34; alt=&#34;Screenshot of running traceroute example.com.&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can see that it took 8 hops to get from my computer to the destination IP (&lt;code&gt;93.184.216.34&lt;/code&gt;), and you can see the associated timing for three different attempts for each step. If we wanted to, we could map out these IPs to see the rough physical path of our packets too; from a quick &lt;code&gt;whois&lt;/code&gt;, it looks like &lt;code&gt;example.com&lt;/code&gt;&amp;rsquo;s server is based in Los Angeles, and there are quite a few ways to get there!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;traceroute&lt;/code&gt; is made possible by a side effect of features in the Internet Protocol (IP). Every &lt;a href=&#34;https://en.wikipedia.org/wiki/IPv4#Header&#34;&gt;IPv4 packet header&lt;/a&gt; includes a number of fields:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;header.png&#34; alt=&#34;Diagram of IPv4 header&#34;&gt;&lt;/p&gt;
&lt;p&gt;One of the fields in the header is a &amp;ldquo;Time To Live&amp;rdquo; value, or TTL. The &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc791#section-1.4&#34;&gt;original DARPA RFC defined Time to Live in 1981 as follows&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Time to Live is an indication of an upper bound on the lifetime of an internet datagram.  It is set by the sender of the datagram and reduced at the points along the route where it is processed.  If the time to live reaches zero before the internet datagram reaches its destination, the internet datagram is destroyed.  The time to live can be thought of as a self destruct time limit.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In theory, this would be measured in seconds, representing a true self-destruct timer, but in practice, the TTL actually reflects the number of hops that the packet can still take on its way to the destination. At each router along the packet&amp;rsquo;s path, this value is decremented, and if it hits zero, the packet goes no further and an ICMP error message is returned to the sender. This helps prevent unintended behavior in a complicated network, such as infinite looping. Without the TTL, you could imagine orphaned and lost packets being forwarded indefinitely.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Time_exceeded&#34;&gt;ICMP &amp;ldquo;Time exceeded&amp;rdquo; message&lt;/a&gt; that is sent back to the original sender includes some basic information, and with the message, the address of the router that sent it is also visible.&lt;/p&gt;
&lt;h3 id=&#34;traceroute&#34;&gt;traceroute&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;traceroute&lt;/code&gt; leverages this decrementing TTL functionality in a clever way to explore the network. Let&amp;rsquo;s say we&amp;rsquo;re trying to trace the route to &lt;code&gt;C&lt;/code&gt; in the following network:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;network.png&#34; alt=&#34;Diagram of a simple network consisting of my laptop and three other nodes: A, B, and C.&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;traceroute&lt;/code&gt; relies on artificially manipulating the TTL of outgoing packets, incrementally sending packets with incrementing TTL values. It starts with a TTL of 1; when that packet gets to the first router, the TTL will be decremented to 0, and the router will send back an ICMP message.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;first-hop.png&#34; alt=&#34;Diagram of a packet being sent from my laptop to A, destined for C with a TTL of 1. A sends back an ICMP message, and I can write down that the first hop is A.&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;traceroute&lt;/code&gt; can then send a packet to the destination with a TTL of 2, and this packet will make it to the second router along the path before an ICMP is sent back.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;second-hop.png&#34; alt=&#34;Diagram of a packet being sent from my laptop to A, destined for C with a TTL of 2. A sends it to B with a TTL of 1. B then sends back an ICMP message, and I can write down that the second hop is B.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Each packet will go one hop further than the last, and by plumbing the router at each hop depth and keeping track of the routers it receives messages from along the way, it can reconstruct a rough path to the destination host.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;third-hop.png&#34; alt=&#34;Diagram of a packet being sent from my laptop to A, destined for C with a TTL of 3. A sends it to B with a TTL of 2. B then sends it to C with a TTL of 3, and C sends back an ICMP message. I can write down that the third hop is C.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;what-would-a-basic-implementation-look-like&#34;&gt;What would a basic implementation look like?&lt;/h3&gt;
&lt;p&gt;In order to recreate this, we&amp;rsquo;ll want to write some code that does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gets the destination IP of the example destination, say &amp;ldquo;example.com&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Creates a socket that allows it to send UDP packets&lt;/li&gt;
&lt;li&gt;Creates a socket on which it&amp;rsquo;ll listen for ICMP messages&lt;/li&gt;
&lt;li&gt;Initializes loop variables for keeping track of the current hop&lt;/li&gt;
&lt;li&gt;Inside a loop while we haven&amp;rsquo;t reached the destination:
&lt;ul&gt;
&lt;li&gt;Sends a packet with an incrementing TTL&lt;/li&gt;
&lt;li&gt;Receives an incoming ICMP packet&lt;/li&gt;
&lt;li&gt;Keeps track of the current hop and the address of the received ICMP message&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;That doesn&amp;rsquo;t sound too bad! This is all pretty approachable, and in Python, it may look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
import socket

DESTINATION = &amp;#39;example.com&amp;#39;
DESTINATION_PORT = 33434
MESSAGE = &amp;#39;foo&amp;#39;


def main():
    # Get the destination ip address.
    destination_ip = socket.gethostbyname(DESTINATION)
    print(&amp;#39;Tracing the route to {0}&amp;#39;.format(destination_ip))

    # Prepare a socket to send UDP packets.
    sending_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sending_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # Prepare a socket to listen for ICMP messages.
    # Note: the middle argument _may_ be SOCK_RAW, but since I&amp;#39;m running this
    # on a mac, I had to use SOCK_DGRAM to avoid needing root privileges.
    # See https://apple.stackexchange.com/questions/312857/how-does-macos-allow-standard-users-to-ping.
    receiving_socket = socket.socket(
        socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)

    # Show the ICMP header, since that&amp;#39;s where the router address is.
    receiving_socket.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1)

    # Initialize the variables we&amp;#39;ll use for seeing if we&amp;#39;re done and keeping
    # track of the current hop.
    received_ip = None
    current_hop = 1
    while received_ip != destination_ip:
        # Set the socket&amp;#39;s TTL to the current hop so that the packet just
        # reaches it before being stopped.
        sending_socket.setsockopt(
            socket.IPPROTO_IP, socket.IP_TTL, current_hop)

        # Attempt to send a UDP packet to the destination ip.
        sending_socket.sendto(bytes(MESSAGE, &amp;#39;utf-8&amp;#39;),
                              (destination_ip, DESTINATION_PORT))

        # Receive any incoming ICMP packet. We can ignore the first return
        # value from recvfrom, which would be the included data.
        _, addr = receiving_socket.recvfrom(1500)
        received_ip = addr[0]
        print(&amp;#39;Current hop {0}: ICMP message received from {1}&amp;#39;.format(
            current_hop, received_ip))
        current_hop = current_hop &amp;#43; 1


if __name__ == &amp;#39;__main__&amp;#39;:
    main()

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;There we go! If I go ahead and run this, I get the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;blog: $ python3 content/posts/toy-traceroute/toy_traceroute.py
Tracing the route to 93.184.216.34
Current hop 1: ICMP message received from 10.1.10.1
Current hop 2: ICMP message received from 96.120.66.197
Current hop 3: ICMP message received from 24.124.212.221
Current hop 4: ICMP message received from 96.108.45.74
Current hop 5: ICMP message received from 96.110.22.85
Current hop 6: ICMP message received from 69.241.119.54
Current hop 7: ICMP message received from 152.195.232.129
Current hop 8: ICMP message received from 93.184.216.34
blog: $
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This matches exactly the path that &lt;code&gt;traceroute&lt;/code&gt; uncovered earlier, which is very heartening! We&amp;rsquo;ve successfully plumbed the route in between my computer and the destination host.&lt;/p&gt;
&lt;h3 id=&#34;complications-that-make-it-interesting&#34;&gt;Complications that make it interesting&lt;/h3&gt;
&lt;p&gt;There are many ways to improve this basic program, including at least the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Timing: how long did each hop take?&lt;/li&gt;
&lt;li&gt;What if we sent multiple tries to have more timing samples?&lt;/li&gt;
&lt;li&gt;What do we do if a hop doesn&amp;rsquo;t respond?&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;&lt;code&gt;traceroute&lt;/code&gt; handles these, but it&amp;rsquo;s always interesting to think about how you&amp;rsquo;d solve the problem yourself.&lt;/p&gt;
&lt;p&gt;There are also a few interesting cases about tracing the route in general:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What if a router drops UDP packets? What if it doesn&amp;rsquo;t send ICMP packets?&lt;/li&gt;
&lt;li&gt;What if two subsequent requests take a different path through the network? Would the path we&amp;rsquo;re tracing really exist?&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;seeing-how-things-work-through-a-toy-problem&#34;&gt;Seeing how things work through a toy problem&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;traceroute&lt;/code&gt; is a simple but clever use case of a built-in feature of the internet, and it&amp;rsquo;s fun to see how it works through a basic example.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;p&gt;Misc. recommended reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;traceroute&lt;/code&gt; &lt;a href=&#34;https://en.wikipedia.org/wiki/Traceroute&#34;&gt;Wikipedia article&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;traceroute&lt;/code&gt; is also turning 35 this year!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/openbsd/src/blob/master/usr.sbin/traceroute/traceroute.c&#34;&gt;traceroute source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cloudflare.com/learning/network-layer/internet-protocol/&#34;&gt;Cloudfare&amp;rsquo;s blog: What is the Internet Protocol?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.measurementlab.net/publications/repair-persistent-route-failures.pdf&#34;&gt;&lt;em&gt;LIFEGUARD: Practical Repair of Persistent Route Failures&lt;/em&gt;, another clever reuse of functionality with BGP to avoid route failures&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>(1) New Message: How websites catch your attention with JavaScript</title>
      <link>https://alexanderell.is/posts/attention-javascript/</link>
      <pubDate>Sat, 22 Jan 2022 11:59:21 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/attention-javascript/</guid>
      <description>&lt;p&gt;There are many good things about JavaScript. There are also bad things about JavaScript.&lt;/p&gt;
&lt;p&gt;There are many cases of well-intentioned features in JavaScript being used for unhelpful purposes. These features can help support rich web applications with impressive features and capabilities, but they can often be used antagonistically, in the sense that they aren&amp;rsquo;t helpful for the user (&lt;a href=&#34;https://alexanderell.is/posts/taking-over-my-clipboard&#34;&gt;like intercepting your clipboard commands&lt;/a&gt;). I find these cases interesting, and I wanted to explore a few of these attention-grabbing examples.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: these were all tested on desktop in Chrome, Firefox, and Safari. If you&amp;rsquo;re on mobile or an unsupported browser, I apologize that you won&amp;rsquo;t get to see the weirdness, and I&amp;rsquo;ve included screenshots and screencasts for your enjoyment.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;1-new-message&#34;&gt;(1) New Message&lt;/h2&gt;
&lt;p&gt;One of the things you have probably seen is how pages play with the page&amp;rsquo;s title, like prepending &amp;ldquo;(1) New Message&amp;rdquo; to the title. There are some valid and helpful use cases for this, such as an online chat app or an email inbox showing the number of unread messages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;inbox-1.png&#34; alt=&#34;Screenshot of gmail tab with &amp;amp;ldquo;(1)&amp;amp;rdquo; prepended to title&#34;&gt;&lt;/p&gt;
&lt;p&gt;Where this gets a little fuzzier is when the page updates it in a way to catch your eye with movement. This is doubly dubious when the message is something eye-catching that you didn&amp;rsquo;t opt in to, such as an unprompted sales message from a chat bot. At that point, it feels like a blinking advertisement desperate to get your attention. As the developer, if you want to be extra sneaky, you can even make it only happen when the user tabs away from the page, creating an extra little push to get them back to your site.&lt;/p&gt;
&lt;p&gt;Give it a shot with the radio button below: turn it on, then open a new tab!&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Blinking the title when hidden:&lt;/b&gt;
&lt;input type=&#34;radio&#34; name=&#34;title-blink&#34; id=&#34;title-blink-off&#34; checked&gt;&lt;label for=&#34;title-blink-off&#34;&gt;Off&lt;/label&gt;&lt;/input&gt;
&lt;input type=&#34;radio&#34; name=&#34;title-blink&#34; id=&#34;title-blink-on&#34;&gt;&lt;label for=&#34;title-blink-on&#34;&gt;On&lt;/label&gt;&lt;/input&gt;&lt;/p&gt;
&lt;script&gt;
  var titleBlinkOff = document.querySelector(&#34;#title-blink-off&#34;);
  var titleBlinkOn = document.querySelector(&#34;#title-blink-on&#34;);
  var originalTitle = document.title;
  var newTitle = &#34;HELP I&#39;M TRAPPED IN A WEBSITE&#34;;

  var titleBlinkInterval;
  titleBlinkOff.onclick = () =&gt; {
    document.title = originalTitle;
    clearInterval(titleBlinkInterval);
  }
  titleBlinkOn.onclick = () =&gt; {
    // Change the title every second
    titleBlinkInterval = setInterval(() =&gt; {
      if (document.hidden) {
        document.title = document.title == originalTitle? newTitle : originalTitle;
      }
    }, 1000);
  }
  // If you click back in, go back to original title.
  document.addEventListener(&#34;visibilitychange&#34;, function() {
    if (!document.hidden) {
      document.title = originalTitle;
    }
  })
&lt;/script&gt;
&lt;details&gt;
  &lt;summary&gt;Spoiler: Blinking the title when hidden&lt;/summary&gt;
  &lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;blinking-title.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;blinking-title.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;/details&gt;
&lt;br&gt;
&lt;p&gt;When I was playing around with this with other values for the title, I even fooled myself — I had tabbed away from my draft blog post for a minute, and my eye was immediately grabbed by the &amp;ldquo;(1)&amp;rdquo; in the title! Funny to see how quickly your conditioned mind gets fooled.&lt;/p&gt;
&lt;h4 id=&#34;how-does-this-work&#34;&gt;How does this work?&lt;/h4&gt;
&lt;p&gt;The main reason this works is because the page&amp;rsquo;s title is accessible through the &lt;code&gt;document&lt;/code&gt; element via &lt;code&gt;document.title&lt;/code&gt;. Because you can assign directly to it, like &lt;code&gt;document.title = &amp;quot;foo&amp;quot;;&lt;/code&gt;, you can easily put it in a continuously running interval with two options, the default title and a new title. Page visibility is also accessible in JavaScript via the &lt;code&gt;document&lt;/code&gt; element. In this case, you can perform a check on &lt;code&gt;document.hidden&lt;/code&gt; to see whether or not the page is currently hidden. If it is, you can go ahead with the title update; if not, you can lay low for now.&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Click to view code&lt;/summary&gt;
  &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;
  var titleBlinkOff = document.querySelector(&amp;#34;#title-blink-off&amp;#34;);
  var titleBlinkOn = document.querySelector(&amp;#34;#title-blink-on&amp;#34;);
  var originalTitle = document.title;
  var newTitle = &amp;#34;HELP I&amp;#39;M TRAPPED IN A WEBSITE&amp;#34;;

  var titleBlinkInterval;
  titleBlinkOff.onclick = () =&amp;gt; {
    document.title = originalTitle;
    clearInterval(titleBlinkInterval);
  }
  titleBlinkOn.onclick = () =&amp;gt; {
    // Change the title every second
    titleBlinkInterval = setInterval(() =&amp;gt; {
      if (document.hidden) {
        document.title = document.title == originalTitle? newTitle : originalTitle;
      }
    }, 1000);
  }
  // If you click back in, go back to original title.
  document.addEventListener(&amp;#34;visibilitychange&amp;#34;, function() {
    if (!document.hidden) {
      document.title = originalTitle;
    }
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br&gt;
&lt;p&gt;More reading: &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Document/title&#34;&gt;&lt;code&gt;document.title&lt;/code&gt; MDN page&lt;/a&gt;, &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Document/hidden&#34;&gt;&lt;code&gt;document.hidden&lt;/code&gt; MDN page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;what-about-the-favicon&#34;&gt;What about the favicon?&lt;/h3&gt;
&lt;p&gt;Another thing you can do, though this is a little more cursed, is changing the page&amp;rsquo;s favicon. I don&amp;rsquo;t think I&amp;rsquo;ve seen this in the wild, thankfully, but depending on your browser (should work in Chrome and Firefox), it may be possible (though you may need an incognito window if your browser has already cached my favicon?). Give the following a try!&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Blinking the favicon:&lt;/b&gt;
&lt;input type=&#34;radio&#34; name=&#34;favicon-blink&#34; id=&#34;favicon-blink-off&#34; checked&gt;
&lt;label for=&#34;favicon-blink-off&#34;&gt;Off&lt;/label&gt;
&lt;/input&gt;
&lt;input type=&#34;radio&#34; name=&#34;favicon-blink&#34; id=&#34;favicon-blink-on&#34;&gt;
&lt;label for=&#34;favicon-blink-on&#34;&gt;On&lt;/label&gt;
&lt;/input&gt;&lt;/p&gt;
&lt;script&gt;
  // This is so dumb, I&#39;m sorry
  // Used some of https://stackoverflow.com/questions/260857/changing-website-favicon-dynamically to create favicon.
  var faviconBlinkOff = document.querySelector(&#34;#favicon-blink-off&#34;);
  var faviconBlinkOn = document.querySelector(&#34;#favicon-blink-on&#34;);
  var headElement = document.getElementsByTagName(&#34;head&#34;)[0];

  var childAppended = false;
  var blankFaviconHref = &#34;blank-favicon.ico&#34;;
  var originalFaviconHref = &#34;/images/favicon.ico&#34;;
  var getNewFavicon = (blank) =&gt; {
    var newFavicon = document.createElement(&#34;link&#34;);
    newFavicon.rel = &#34;icon&#34;;
    newFavicon.href = blank ? blankFaviconHref : originalFaviconHref;
    return newFavicon;
  }
  var faviconBlinkInterval;
  var blankFavicon;
  var originalFavicon;
  var resetFavicon = () =&gt; {
    if (blankFavicon &amp;&amp; headElement.lastChild == blankFavicon) {
      headElement.removeChild(blankFavicon);
    }
    originalFavicon = getNewFavicon(false);
    headElement.appendChild(originalFavicon);
  }
  var addNewFavicon = () =&gt; {
    if (originalFavicon) {
      headElement.removeChild(originalFavicon);
    }
    blankFavicon = getNewFavicon(true);
    headElement.appendChild(blankFavicon);
  }
  faviconBlinkOff.onclick = () =&gt; {
    clearInterval(faviconBlinkInterval);
    resetFavicon();
  };
  faviconBlinkOn.onclick = () =&gt; {
    faviconBlinkInterval = setInterval(() =&gt; {
      if (childAppended) {
        childAppended = false;
        resetFavicon();
      } else {
        childAppended = true;
        addNewFavicon();
      }
    }, 1000);
  };
&lt;/script&gt;
&lt;details&gt;
  &lt;summary&gt;Spoiler: Blinking the favicon&lt;/summary&gt;
  &lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;blinking-favicon.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;blinking-favicon.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;/details&gt;
&lt;br&gt;
&lt;h4 id=&#34;how-does-this-work-1&#34;&gt;How does this work?&lt;/h4&gt;
&lt;p&gt;This one is a little weirder. As far as I can tell, because of the way the browsers parse favicon icons, the last icon element in the HTML &lt;code&gt;head&lt;/code&gt; that hasn&amp;rsquo;t already been parsed is used. You can easily create a new &lt;code&gt;link&lt;/code&gt; element, give it an &lt;code&gt;icon&lt;/code&gt; &lt;code&gt;ref&lt;/code&gt; and an appropriate &lt;code&gt;href&lt;/code&gt;, and add it to the page&amp;rsquo;s &lt;code&gt;head&lt;/code&gt;. This works for changing from the default icon to a new icon, but I ran into an issue when trying to go back to the default, where 1) I couldn&amp;rsquo;t just delete the new one, as the browser wouldn&amp;rsquo;t go back to parse the original and 2) I couldn&amp;rsquo;t just append a new version of the old one. Instead, I&amp;rsquo;m doing a weird trick where I&amp;rsquo;m recreating and removing the favicon icon links accordingly to re-parse them; this ends up looking as if it&amp;rsquo;s switching between the two. You can see that in the debug HTML:&lt;/p&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;html-view.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;html-view.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;br&gt;
&lt;p&gt;As I mentioned earlier, definitely a little more cursed!&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Click to view code&lt;/summary&gt;
  &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;
  // This is so dumb, I&amp;#39;m sorry
  // Used some of https://stackoverflow.com/questions/260857/changing-website-favicon-dynamically to create favicon.
  var faviconBlinkOff = document.querySelector(&amp;#34;#favicon-blink-off&amp;#34;);
  var faviconBlinkOn = document.querySelector(&amp;#34;#favicon-blink-on&amp;#34;);
  var headElement = document.getElementsByTagName(&amp;#34;head&amp;#34;)[0];

  var childAppended = false;
  var blankFaviconHref = &amp;#34;blank-favicon.ico&amp;#34;;
  var originalFaviconHref = &amp;#34;/images/favicon.ico&amp;#34;;
  var getNewFavicon = (blank) =&amp;gt; {
    var newFavicon = document.createElement(&amp;#34;link&amp;#34;);
    newFavicon.rel = &amp;#34;icon&amp;#34;;
    newFavicon.href = blank ? blankFaviconHref : originalFaviconHref;
    return newFavicon;
  }
  var faviconBlinkInterval;
  var blankFavicon;
  var originalFavicon;
  var resetFavicon = () =&amp;gt; {
    if (blankFavicon &amp;amp;&amp;amp; headElement.lastChild == blankFavicon) {
      headElement.removeChild(blankFavicon);
    }
    originalFavicon = getNewFavicon(false);
    headElement.appendChild(originalFavicon);
  }
  var addNewFavicon = () =&amp;gt; {
    if (originalFavicon) {
      headElement.removeChild(originalFavicon);
    }
    blankFavicon = getNewFavicon(true);
    headElement.appendChild(blankFavicon);
  }
  faviconBlinkOff.onclick = () =&amp;gt; {
    clearInterval(faviconBlinkInterval);
    resetFavicon();
  };
  faviconBlinkOn.onclick = () =&amp;gt; {
    faviconBlinkInterval = setInterval(() =&amp;gt; {
      if (childAppended) {
        childAppended = false;
        resetFavicon();
      } else {
        childAppended = true;
        addNewFavicon();
      }
    }, 1000);
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br&gt;
&lt;p&gt;Further reading: &lt;a href=&#34;https://en.wikipedia.org/wiki/Favicon&#34;&gt;Wikipedia article on favicons&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;are-you-sure-you-want-to-leave&#34;&gt;Are you sure you want to leave?&lt;/h2&gt;
&lt;p&gt;Sometimes when you try to navigate away from a page, you&amp;rsquo;ll get a popup asking you if you&amp;rsquo;re sure you want to go. There are some cases where this can be helpful. Maybe you&amp;rsquo;re halfway through a long comment on your favorite social media site and you accidentally hit control-w. Rather than lose your unsaved progress, that helpful intermediate step can let you cancel the action and get back to your typing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;are-you-sure.png&#34; alt=&#34;Screenshot of the &amp;amp;ldquo;Leave site?&amp;amp;rdquo; alert&#34;&gt;&lt;/p&gt;
&lt;p&gt;The downside is that like everything else, there&amp;rsquo;s nothing stopping pages from doing this when it isn&amp;rsquo;t helpful. For example, maybe I want to do everything I can to keep people from leaving my blog (note: I don&amp;rsquo;t — please leave at your leisure!). Try turning on the following radio option and closing this tab.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Confirm before leaving:&lt;/b&gt;
&lt;input type=&#34;radio&#34; name=&#34;leave-confirmation&#34; id=&#34;leave-confirmation-off&#34; checked&gt;
&lt;label for=&#34;leave-confirmation-off&#34;&gt;Off&lt;/label&gt;
&lt;/input&gt;
&lt;input type=&#34;radio&#34; name=&#34;leave-confirmation&#34; id=&#34;leave-confirmation-on&#34;&gt;
&lt;label for=&#34;leave-confirmation-on&#34;&gt;On&lt;/label&gt;
&lt;/input&gt;&lt;/p&gt;
&lt;script&gt;
  // Shout out to https://stackoverflow.com/questions/1119289/how-to-show-the-are-you-sure-you-want-to-navigate-away-from-this-page-when-ch
  var leaveConfirmationOff = document.querySelector(&#34;#leave-confirmation-off&#34;);
  var leaveConfirmationOn = document.querySelector(&#34;#leave-confirmation-on&#34;);

  leaveConfirmationOff.onclick = () =&gt; {
    window.onbeforeunload = null;
  }

  leaveConfirmationOn.onclick = () =&gt; {
    window.onbeforeunload = function() {
      return true;
    };
  }
&lt;/script&gt;
&lt;h4 id=&#34;how-does-this-work-2&#34;&gt;How does this work?&lt;/h4&gt;
&lt;p&gt;This one is much more straightforward. By adding a function that fires for the &lt;code&gt;beforeunload&lt;/code&gt; event, we can ask the browser to check with the user before they leave the page. Browsers frequently require you to first interact with the page, but if you clicked the radio button above, you would have already done so. We can mess with this event easily by setting the &lt;code&gt;window.onbeforeunload&lt;/code&gt; function to do a simple &lt;code&gt;return true;&lt;/code&gt;.&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Click to view code&lt;/summary&gt;
  &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;  // Shout out to https://stackoverflow.com/questions/1119289/how-to-show-the-are-you-sure-you-want-to-navigate-away-from-this-page-when-ch
  var leaveConfirmationOff = document.querySelector(&amp;#34;#leave-confirmation-off&amp;#34;);
  var leaveConfirmationOn = document.querySelector(&amp;#34;#leave-confirmation-on&amp;#34;);

  leaveConfirmationOff.onclick = () =&amp;gt; {
    window.onbeforeunload = null;
  }

  leaveConfirmationOn.onclick = () =&amp;gt; {
    window.onbeforeunload = function() {
      return true;
    };
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br&gt;
&lt;p&gt;Further reading: &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload&#34;&gt;MDN page on &lt;code&gt;WindowEventHandlers.onbeforeunload&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event&#34;&gt;beforeunload event&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-can-we-do-with-the-user-agent&#34;&gt;What can we do with the user agent?&lt;/h2&gt;
&lt;p&gt;Sometimes when you&amp;rsquo;re on a website, you can see what looks like an alert from your computer. For example, if you&amp;rsquo;re surfing the web on a Mac, you may see what looks like a Mac alert about software being out of date on your computer:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;full-screen-phish.png&#34; alt=&#34;Phishing screenshot showing what looks like a Mac system alert about software being out of date&#34;&gt;
&lt;em&gt;Screenshot from the &lt;a href=&#34;https://community.adobe.com/t5/flash-player-discussions/beware-of-fake-flash-player-update-on-osx/td-p/9826832&#34;&gt;Adobe Support Forums&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To an unknowing user, it looks like your Mac is telling you that your software is out of date. In fact, it actually looks like my &amp;ldquo;Leave site?&amp;rdquo; alert above, which came from my trusted computer&amp;rsquo;s Chrome. Unlike most websites, your Mac is generally a trustable friend that frequently warns you about risks and tries to keep you safe.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s probably actually happening is that the website has checked what operating system you&amp;rsquo;re on (either in the server or in client code) and shown an alert with custom styles that look like that system&amp;rsquo;s alerts. You could do that a number of ways, but an easy way would be with JavaScript if you wanted to do it with a dynamic pop up. Click the button below; I&amp;rsquo;m just doing an alert, but you could imagine I could easily add an HTML element with the appropriate CSS classes to mimic your system.&lt;/p&gt;
&lt;p&gt;&lt;button id=&#34;show-user-agent&#34;&gt;Show user agent&lt;/button&gt;&lt;/p&gt;
&lt;script&gt;
  var userAgentButton = document.querySelector(&#34;#show-user-agent&#34;);
  var userAgents = [
    &#34;iPhone&#34;,
    &#34;iPad&#34;,
    &#34;Mac&#34;,
    &#34;Android&#34;,
    &#34;Linux&#34;,
    &#34;Windows&#34;,
  ]
  userAgentButton.onclick = () =&gt; {
    for (const userAgent of userAgents) {
      if (navigator.userAgent.indexOf(userAgent) !== -1) {
        alert(&#34;This is when I&#39;d add a popup HTML element with &#34; + userAgent + &#34; styles.&#34;);
        return;
      }
    }
    alert(&#34;Sorry, I wasn&#39;t checking for your userAgent. You&#39;re safe today!&#34; );
  }
&lt;/script&gt;
&lt;h4 id=&#34;how-does-this-work-3&#34;&gt;How does this work?&lt;/h4&gt;
&lt;p&gt;This one is also not as exciting. The userAgent is easily available via &lt;code&gt;navigator.userAgent&lt;/code&gt;, and we can check a few values to see what would match. For instance, we could look for the string &lt;code&gt;&amp;quot;Mac&amp;quot;&lt;/code&gt; in the userAgent, and if it&amp;rsquo;s there, we could add an element with Mac styles. The trick here would be in the CSS, since we&amp;rsquo;d (presumably) want to make it match the system and not be too out of date.&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Click to view code&lt;/summary&gt;
  &lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;  var userAgentButton = document.querySelector(&amp;#34;#show-user-agent&amp;#34;);
  var userAgents = [
    &amp;#34;iPhone&amp;#34;,
    &amp;#34;iPad&amp;#34;,
    &amp;#34;Mac&amp;#34;,
    &amp;#34;Android&amp;#34;,
    &amp;#34;Linux&amp;#34;,
    &amp;#34;Windows&amp;#34;,
  ]
  userAgentButton.onclick = () =&amp;gt; {
    for (const userAgent of userAgents) {
      if (navigator.userAgent.indexOf(userAgent) !== -1) {
        alert(&amp;#34;This is when I&amp;#39;d add a popup HTML element with &amp;#34; + userAgent + &amp;#34; styles.&amp;#34;);
        return;
      }
    }
    alert(&amp;#34;Sorry, I wasn&amp;#39;t checking for your userAgent. You&amp;#39;re safe today!&amp;#34; );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;br&gt;
&lt;p&gt;Further reading: &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent&#34;&gt;MDN page on &lt;code&gt;Navigator.userAgent&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;be-aware&#34;&gt;Be aware&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve seen most of these out in the wild, and I&amp;rsquo;m sure you have too. Like many things, JavaScript can be used for good or for bad. These capabilities allow for a powerful and feature filled web, but they also leave room for abuse. It&amp;rsquo;s already hard to protect your attention, but it&amp;rsquo;s helpful to think about how people try to pry at yours even more.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How do you visualize code?</title>
      <link>https://alexanderell.is/posts/visualizing-code/</link>
      <pubDate>Mon, 17 Jan 2022 17:11:31 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/visualizing-code/</guid>
      <description>&lt;p&gt;The other day I was reading about the development of personal computers and the desktop GUI, and I was thinking about how comfortable we all have become with the analogy of the &amp;ldquo;desktop&amp;rdquo; for our personal computers. We place files in folders and keep them on our desktops. There&amp;rsquo;s a lot of physical action going on.&lt;/p&gt;
&lt;p&gt;Humans are very good at &lt;a href=&#34;https://en.wikipedia.org/wiki/Spatial_ability&#34;&gt;understanding spaces&lt;/a&gt;, especially when it comes to &lt;a href=&#34;https://en.wikipedia.org/wiki/Spatial_memory&#34;&gt;remembering physical spaces&lt;/a&gt;, and it got me thinking about how we commonly visualize code. Is there any good way to tap into that when thinking about and visualizing code?&lt;/p&gt;
&lt;h2 id=&#34;how-do-you-visualize-code&#34;&gt;How do you visualize code?&lt;/h2&gt;
&lt;p&gt;This made me think about how I tend to visualize code, and it&amp;rsquo;s a little hard to describe. I think it usually lives in my head in a variety of ways, depending on the level of abstraction and specificity, and there&amp;rsquo;s some combination of all of them there at the same time. Depending on the need, I can jump between them pretty well, especially if I&amp;rsquo;m very familiar with the codebase.&lt;/p&gt;
&lt;p&gt;For example, when I&amp;rsquo;m imagining the interactions between various microservices, it&amp;rsquo;s helpful to draw big boxes around each service, treating each one like a big unit of work with RPC interactions in between.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;architecture-diagram.png&#34; alt=&#34;Architecture diagram of a simple microservice-oriented car rental service.&#34;&gt;&lt;/p&gt;
&lt;center&gt;Architecture diagram of a simple microservice-oriented car rental service and the accompanying distributed trace that might represent a path through it.&lt;/center&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;On the other end of the spectrum, if I&amp;rsquo;m worried about the minuscule specifics of how the computer is reading what I&amp;rsquo;ve given it, it&amp;rsquo;s helpful to zoom all the way in to a representation of the physical memory.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;assembly.png&#34; alt=&#34;Screenshot of a Google Sheets page where I&amp;amp;rsquo;ve added assembly code in the various cells for walking through it carefully&#34;&gt;&lt;/p&gt;
&lt;center&gt;Screenshot of a Google Sheets page I once made with memory addresses and assembly in the various cells. This was helpful for walking through it very, very carefully.&lt;/center&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;Luckily I spend most of my time somewhere in the middle, with some balance between reading the actual code (luckily higher level than assembly), thinking about chunks of code as large units, and working with architecture diagrams and inter-system communication. Even code on its own has a lot of physical relationships already built in; think of directory paths, namespaces, line indentation, and the linear ordering of lines of code,&lt;/p&gt;
&lt;h2 id=&#34;how-well-do-these-visualizations-do&#34;&gt;How well do these visualizations do?&lt;/h2&gt;
&lt;p&gt;Thinking about this made me consider a few different visualization techniques, each of which is useful in different circumstances. Consider the following incomplete list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Architecture diagrams&lt;/li&gt;
&lt;li&gt;Dependency graphs&lt;/li&gt;
&lt;li&gt;Distributed traces&lt;/li&gt;
&lt;li&gt;Sequence diagrams&lt;/li&gt;
&lt;li&gt;Class diagrams&lt;/li&gt;
&lt;li&gt;Print statements&lt;/li&gt;
&lt;li&gt;Flame graphs&lt;/li&gt;
&lt;li&gt;Reading the source code&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;How would we compare these? First, there seems to be a natural axis of abstractness, ranging from low level code reading to high level architecture diagrams. There seem to be a few other axes we could order them on as well.&lt;/p&gt;
&lt;p&gt;Maybe we could rank them on how well they represent the larger system? Architecture diagrams would do well there, but a flame graph would only represent a single execution path. Maybe frequency of change? That would be an interesting one, with architecture diagrams (hopefully) staying pretty static as the source code changes frequently.&lt;/p&gt;
&lt;p&gt;What if we thought about how well the visualization represents the actual code execution through the system? In this case, maybe a high level architecture diagram wouldn&amp;rsquo;t score very high, as it would abstract away many of the details inside the service boxes. Distributed tracing could do better, though the level of specificity would depend on the number of tracepoints you have. Class diagrams, while helpful for visualizing the relationship between classes, may not represent the actual paths taken through. Flame graphs would show a clear execution path, but again they would only show a single code path without giving visibility to the larger system.&lt;/p&gt;
&lt;p&gt;Graphing these &lt;em&gt;might&lt;/em&gt; look something like the following, (though each of these dots may be better represented as a range):&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph.png&#34; alt=&#34;Ranking the above visualizations on a scale from high level to low level along the x axis and &amp;amp;ldquo;visualization of actual execution&amp;amp;rdquo; on the y axis&#34;&gt;
&lt;a href=&#34;graph.png&#34;&gt;Link to a larger version of the graph&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This made me think about what that upper right section would be like. What would a helpful visualization look like that provided clear insight into the details? Is there a way to visualize the path through the larger system while staying at a low level?&lt;/p&gt;
&lt;h2 id=&#34;what-would-it-look-like-if-we-tried-to-use-space-as-well&#34;&gt;What would it look like if we tried to use space as well?&lt;/h2&gt;
&lt;p&gt;Looking at a diagram still feels like reading a map. There&amp;rsquo;s that moment of spatially translating what you see in the code to the diagram, much like you&amp;rsquo;d orient yourself with a map in an unfamiliar area. Like the desktop metaphor for things on our computers, I wonder if there&amp;rsquo;s another way to visualize code as things that exist in the world to make the translation easier.&lt;/p&gt;
&lt;p&gt;What if we had a toy visualization where we represented code in physical space?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have the following basic example, where we have a &lt;code&gt;Counter&lt;/code&gt; class that keeps track of a private &lt;code&gt;count_&lt;/code&gt; variable and allows various access methods. Maybe it&amp;rsquo;s accessed in a simple &lt;code&gt;main&lt;/code&gt; function to do some counting.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

class Counter {
public:
  void reset() {
    count_ = 0;
  }

  int get() {
    return count_;
  }

  void incr() {
    ++count_;
  }

  void set_count(int x) {
    count_ = x;
  }

private:
  int count_;
};

int main() {
  Counter counter;
  counter.reset();
  counter.incr();
  counter.incr();
  std::cout &amp;lt;&amp;lt; counter.get();
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What if we represented different sections of the code as 3D structures? What if we represented the Counter class as a big room with incantations written on the wall? You could imagine the possible paths through the code, connections between variables and the things they represent, as pink string:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;counter.png&#34; alt=&#34;The above counter class as a series of posters on a wall with a count_ poster reading 47.&#34;&gt;
&lt;a href=&#34;counter.png&#34;&gt;Link to a larger version&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What could we see with this? The first is that &lt;code&gt;count_&lt;/code&gt; is clearly used appropriately as a private variable, as there aren&amp;rsquo;t any pink strings from it that leave the room. The public methods are all pretty clear as well, with their pink connections to things outside of the room.&lt;/p&gt;
&lt;p&gt;More interestingly, what would it look like with this room connected to another? The &lt;code&gt;main&lt;/code&gt; function could be another room, with its incantation lines connected accordingly:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;main-and-counter.png&#34; alt=&#34;Two separate rooms, Main and Counter, with the code in the main function in the left room connected to the corresponding call in the Counter room via pink strings&#34;&gt;
&lt;a href=&#34;main-and-counter.png&#34;&gt;Link to a larger version&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now we&amp;rsquo;ve got cross-room talk; where the &lt;code&gt;main&lt;/code&gt; function calls &lt;code&gt;counter.reset()&lt;/code&gt;, we could have a connection from the caller in &lt;code&gt;main&lt;/code&gt; to the &lt;code&gt;callee&lt;/code&gt; in the Counter class. You could even imagine a debugger stepping through this, watching pulses being sent across the wires with arguments and return values. Imagine being able to zoom out to a different area to see local state and values, then follow the call paths back through to the active area.&lt;/p&gt;
&lt;h3 id=&#34;would-this-be-helpful&#34;&gt;Would this be helpful?&lt;/h3&gt;
&lt;p&gt;Would something like this be helpful? I&amp;rsquo;m not sure. It would be interesting to walk through a familiar codebase with something like this, especially if you were able to explore spatially in a 3D representation (or in VR?), being able to zoom in and out as you&amp;rsquo;d like. I wonder if it would be especially useful when exploring a new codebase for the first time to see how things are connected, though I&amp;rsquo;ve definitely dealt with some codebases that I imagine would be very intimidating in a view like this. I&amp;rsquo;d love to see what a diff looked like in this view, where a new class is introduced with new connections here and there.&lt;/p&gt;
&lt;p&gt;That being said, I think you&amp;rsquo;d run into at least the following problems when making something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Complicated code is hard enough to reason through. It might be cathartic to visualize the spaghetti in spaghetti code, but how busy would this be with very complicated code?&lt;/li&gt;
&lt;li&gt;How would we be able to represent things like two threads executing at the same time?&lt;/li&gt;
&lt;li&gt;How would you represent passing by reference instead of passing by value?&lt;/li&gt;
&lt;li&gt;How would you represent asynchronous work?&lt;/li&gt;
&lt;li&gt;How would you represent recursion? Rooms all the way down?&lt;/li&gt;
&lt;li&gt;How would you prevent something from this from getting stale and out of date? At the very least, it would need to be automatically generated.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;problems-at-the-intersection-of-space-and-code&#34;&gt;Problems at the intersection of space and code&lt;/h3&gt;
&lt;p&gt;There are a few considerations that make this tricky. One is that physical locations usually change on much longer timeframes than code does. Part of the reason it&amp;rsquo;s useful to use a physical location you know well for a &lt;a href=&#34;https://en.wikipedia.org/wiki/Method_of_loci&#34;&gt;memory palace&lt;/a&gt; is because it&amp;rsquo;s the same every time in your memory. If you&amp;rsquo;re &lt;a href=&#34;https://www.inc.com/jeff-haden/how-to-memorize-an-entire-deck-of-cards-guaranteed-supercharge-your-memory.html&#34;&gt;memorizing a deck of cards&lt;/a&gt;, you can temporarily store the ace of clubs behind the cupboard door, but the cupboard door will still be there the next time you need to store a card.&lt;/p&gt;
&lt;p&gt;If your codebase is changing frequently, your map of how things are laid out spatially may change, regardless of whether those maps are 3D generated or purely mental. It&amp;rsquo;s like returning to an area you used to know, except instead of just the landmarks changing, imagine if the roads were rerouted as well. Even if we&amp;rsquo;re naturally gifted at remembering things spatially, if that space changes, would we still be able to benefit from visualizing that space if we had to relearn it?&lt;/p&gt;
&lt;p&gt;It would be interesting to see how helpful something like this would be for both exploring a new code base (like using a map to explore a new city) and revisiting that codebase as it changes over time (like going back to your hometown after a long time away).&lt;/p&gt;
&lt;h2 id=&#34;visualizing-code&#34;&gt;Visualizing Code&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not very familiar with this area of data visualization (nor the rest of data visualization, really), but in a brief search (AKA 30 minutes of Googling), I found a few projects that seem like they&amp;rsquo;re doing something similar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://softvis3d.com/&#34;&gt;SoftVis3D&lt;/a&gt;: where a &lt;em&gt;&amp;quot;&amp;lsquo;code city&amp;rsquo; view provides a visualization for the hierarchical structure of the project&amp;quot;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/pdf/1708.02174.pdf&#34;&gt;Code Park: A New 3D Code Visualization Tool&lt;/a&gt; (2017), a &lt;em&gt;&amp;ldquo;novel tool for visualizing codebases in a 3D game-like environment&amp;rdquo;&lt;/em&gt; with code represented as &lt;em&gt;&amp;ldquo;code rooms&amp;rdquo;&lt;/em&gt; with code on the walls (sounds very similar now that I read this).&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://opus-htw-aalen.bsz-bw.de/frontdoor/deliver/index/docId/658/file/ICCSE16-SEE.pdf&#34;&gt;Code Structure Visualization Using 3D-Flythrough&lt;/a&gt; (2016), with spatial metaphors and first-person exploration of code.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://primitive.io/&#34;&gt;Primitive&lt;/a&gt;, a VR collaboration startup with a Matrix-looking &lt;em&gt;&amp;ldquo;Immersive Development Environment&amp;rdquo;&lt;/em&gt; with &lt;em&gt;&amp;ldquo;new tools for visually analyzing software in 3D&amp;rdquo;&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;&lt;em&gt;Edit: here are a few more that readers have pointed out:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://appland.com/docs/how-to-use-appmap-diagrams.html&#34;&gt;AppMap&lt;/a&gt;, an automated code analysis tool that includes dependency maps and trace views.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/plurid/plurid&#34;&gt;plurid&lt;/a&gt;, a framework for visualizing and debugging code in a 3D explorable structure.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Fsn_(file_manager)&#34;&gt;fsn (file manager)&lt;/a&gt;, an experimental application to view a file system in 3D (featured in &lt;em&gt;Jurassic Park&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;If you&amp;rsquo;re aware of any others, please get in touch; I&amp;rsquo;d love to learn more!&lt;/p&gt;
&lt;h2 id=&#34;fun-to-imagine&#34;&gt;Fun to imagine&lt;/h2&gt;
&lt;p&gt;This concept is clearly not groundbreaking, but I think it&amp;rsquo;s a fun way to think about how we work with our tools and, importantly, how we might do better. There must be better ways out there, and it&amp;rsquo;s fun to imagine what they might be.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A Children&#39;s Book Story About Distributed Systems</title>
      <link>https://alexanderell.is/posts/terrier-dist-sys/</link>
      <pubDate>Wed, 12 Jan 2022 20:07:04 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/terrier-dist-sys/</guid>
      <description>&lt;p&gt;&lt;em&gt;Editor&amp;rsquo;s note: the following document was found in a collection of papers and assignments, apparently dating from late 2020 when the author was stuck inside studying distributed systems. An additional note, written in green and red Crayon on the back of a CVS receipt, was found stapled to the document: &amp;ldquo;it is now winter . this work features only approximate explanations. it favors silliness over accuracy in some sections. it should be taken with a light hearted grain of salt&amp;rdquo; [sic].&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The following is a verbatim reproduction.&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;karl-and-the-three-dilemmas&#34;&gt;Karl and the Three Dilemmas&lt;/h1&gt;
&lt;h2 id=&#34;karl-sends-a-letter&#34;&gt;Karl sends a letter&lt;/h2&gt;
&lt;p&gt;Karl is a small terrier who lives in Cambridge, Massachusetts. One wintery day in Cambridge, Karl was looking for something to do. He had already eaten a snack, gone for a walk, chewed a toy, played fetch, and taken a nap, but it was only 2:00PM! He was very bored.&lt;/p&gt;
&lt;img src=&#34;karl.png&#34; alt=&#34;A drawing of a small terrier.&#34; /&gt;
&lt;p&gt;As he was day-dreaming about great snacks he had in the past, he remembered his good friend, Jake, who he hadn&amp;rsquo;t seen in a while. &amp;ldquo;I know!&amp;rdquo; exclaimed Karl. &amp;ldquo;I&amp;rsquo;ll see how Jake is doing!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Unfortunately, Karl didn&amp;rsquo;t remember Jake&amp;rsquo;s phone number, but he did remember where he lived, since he would always meet him outside to go for walks together. How would he get in touch with Jake?&lt;/p&gt;
&lt;p&gt;Karl remembered another friend, Andrew Birrell, who was &lt;a href=&#34;http://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf&#34;&gt;great at talking to friends, even when they were far apart&lt;/a&gt;. Karl called Andrew to see if he had any ideas.&lt;/p&gt;
&lt;img src=&#34;karl-is-calling.png&#34; alt=&#34;A picture of an iPhone with an incoming call from Karl in Cambridge.&#34; /&gt;
&lt;p&gt;&amp;ldquo;Hello?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Andrew, it&amp;rsquo;s Karl!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Karl, how are you?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I&amp;rsquo;m doing well! I had a question for you: I want to ask my friend Jake how he&amp;rsquo;s doing, but I don&amp;rsquo;t remember his telephone number. I only have his address. Do you have any ideas for how to get in touch?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Sure, Karl, it&amp;rsquo;s easy! First, you&amp;rsquo;ll want to figure out what question you want to ask, and write that down on a piece of paper. That way, Jake will know what you want to know. After that, you&amp;rsquo;ll want to give the letter to your mom, who will put your letter in an envelope and write Jake&amp;rsquo;s address on it and your address smaller. Don&amp;rsquo;t forget to put a stamp on it!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I won&amp;rsquo;t!&amp;rdquo;, said Karl.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Next,&amp;rdquo; said Andrew, &amp;ldquo;you&amp;rsquo;ll want to go put it in a mailbox. A postal worker will come get it, then it&amp;rsquo;ll get sent across the state to different warehouses and finally to Jake&amp;rsquo;s house. Once it gets to Jake&amp;rsquo;s house, his mom will open the letter and give it to Jake, who will read your question, respond, and send it right back the same way. It&amp;rsquo;ll almost be as if you&amp;rsquo;re passing notes to Jake in the same room!&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;mail-system.png&#34; alt=&#34;A drawing of a letter going from Karl, to his Mom, into an envelope, into a mailbox, into a USPS truck, into another mailbox, out of an Envelope, to Jake&#39;s Mom, and finally to Jake, a black lab.&#34; /&gt;
&lt;p&gt;&amp;ldquo;That sounds so easy!&amp;rdquo; said Karl. &amp;ldquo;So we don&amp;rsquo;t have to worry about how it gets to Jake?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Nope!&amp;rdquo; replied Andrew. &amp;ldquo;You just have to write your question, then your mom will take care of writing Jake&amp;rsquo;s address and getting it to the post office! It&amp;rsquo;s really handy to have the post office, since they do the mail delivery for everyone. Otherwise, every time you wanted to write a letter to a friend, you&amp;rsquo;d have to worry about how to get it there yourself!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That sounds great! Thank you so much Andrew!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;You&amp;rsquo;re welcome Karl!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So Karl went and got a piece of paper, wrote his question, handed it to his mom who then got an envelope and a stamp, put the letter the envelope, wrote Jake&amp;rsquo;s address on the envelope, wrote his own address smaller in the top left, put the stamp on, and dropped it off at the post office.&lt;/p&gt;
&lt;p&gt;A week later, a letter came for Karl!&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Dear Karl, thank you for writing! I was really happy to get your letter. I am doing very well! My mom knit me a sweater for Christmas. Hope you&amp;rsquo;re doing well too! Your friend, Jake.&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;hi.png&#34; alt=&#34;A drawing of Karl, a terrier, saying &#39;Hi!&#39; to Jake, a black lab.&#34; /&gt;
&lt;p&gt;It was just like they were in the same room together!&lt;/p&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2 id=&#34;karl-borrows-a-book-from-the-library&#34;&gt;Karl borrows a book from the library&lt;/h2&gt;
&lt;p&gt;One day on his holiday break from puppy-school, Karl decided he wanted to read a book, but he had already read all of the books in his house. He had heard about a new book, &lt;em&gt;Go, Dog. Go!&lt;/em&gt;, from his friend Shiro, and he asked his mom if they had a copy.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I don&amp;rsquo;t think so, Karl. Why don&amp;rsquo;t you see if you can rent it from the library?&amp;rdquo; asked his mom.&lt;/p&gt;
&lt;img src=&#34;library.png&#34; alt=&#34;A drawing of a public library.&#34; /&gt;
&lt;p&gt;The library! Karl had walked past it before, but he had never been inside. Since his mom was busy, Karl decided to ask his friend Sanjay about renting books from the library, since &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf&#34;&gt;he knew a lot about libraries, filing, and how to organize things&lt;/a&gt;. Karl gave Sanjay a call.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hello?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Sanjay, it&amp;rsquo;s Karl!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Karl! How are you?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I&amp;rsquo;m good, thank you very much! I had a question for you: how do you rent a book from the library?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s a good question Karl. It&amp;rsquo;s great to borrow a book from your local library. You used to be able to go to the library and pick a book in person, but it&amp;rsquo;s a little different these days.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Oh yeah?&amp;rdquo; asked Karl.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Yeah,&amp;rdquo; replied Sanjay. &amp;ldquo;Now, you have to write a letter to the head librarian and ask if you can check out the book. They will look up your library card information to see if you can borrow the book, and if you can, they will write back with the address of the library branch that has that book. You can then write a letter to that library asking for the book, and they&amp;rsquo;ll send it to you.&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;library-to-dog.png&#34; alt=&#34;A drawing two libraries and a dog house. There are letters going between the libraries and the dog house.&#34; /&gt;
&lt;p&gt;&amp;ldquo;That sounds pretty easy,&amp;rdquo; said Karl.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;It definitely works!&amp;rdquo; said Sanjay. &amp;ldquo;Then, if you ever want to borrow the same book again after you return it, you can write another letter directly to the library branch that has it. I&amp;rsquo;ve rented the same book many times.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s great!&amp;rdquo; said Karl. &amp;ldquo;I&amp;rsquo;ve heard really good things about &lt;em&gt;Go, Dog. Go!&lt;/em&gt;, and I may want to read it a lot. I am curious though: how would the libraries get new books?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s a good question, Karl,&amp;rdquo; said Sanjay. &amp;ldquo;If someone wanted to give the library copies of a new book, they&amp;rsquo;d write a letter to the head librarian. If the library can accept the books, the librarian will send back the address of a branch library to send the books to. Once the library gets the books, it can forward on a copy to a few different branches, and those libraries will let the librarian know they have a copy. That way, if anyone else wants to borrow the book, the librarian can send them the address of the closest library with a copy in stock.&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;inter-library.png&#34; alt=&#34;A drawing of three libraries exchanging letters and packages.&#34; /&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s pretty neat!&amp;rdquo; said Karl. &amp;ldquo;That sounds like a lot of letters back and forth, and I&amp;rsquo;m really glad the post office knows how to deliver all of those letters. Now that I think about it, it&amp;rsquo;s really not that different from borrowing a book from one library through the mail, is it?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s exactly right!&amp;rdquo; responded Sanjay. &amp;ldquo;Just like writing a letter to your friend is like passing them a note, it&amp;rsquo;s like we&amp;rsquo;re borrowing a book from one big library, even though it&amp;rsquo;s actually a bunch of small libraries working together with the head librarian.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That makes sense!  I think I&amp;rsquo;m going to go write the librarian a message now. Thanks Sanjay!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;No problem Karl!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So Karl went and wrote the librarian a letter asking if he could borrow &lt;em&gt;Go, Dog. Go!&lt;/em&gt;.&lt;/p&gt;
&lt;img src=&#34;karl-writing.png&#34; alt=&#34;A drawing of Karl, a small terrier, writing a letter.&#34; /&gt;
&lt;p&gt;A little later, the librarian responded saying he could borrow it, and that he should write a letter to the Central Square branch of the Cambridge Public Library. He wrote them a letter, and they sent him the book.&lt;/p&gt;
&lt;p&gt;He read it the same day, and he decided it was his new favorite book.&lt;/p&gt;
&lt;img src=&#34;books.png&#34; alt=&#34;A drawing of two stacked books.&#34; /&gt;
&lt;br&gt;
&lt;br&gt;
&lt;h2 id=&#34;karl-makes-an-advent-calendar&#34;&gt;Karl makes an advent calendar&lt;/h2&gt;
&lt;p&gt;One day in late November, Karl wrote a letter to his local library to borrow &lt;em&gt;Go, Dog. Go!&lt;/em&gt; again, since it was his favorite book. This time, when the book came, a note came with it:&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Have a good idea for an advent calendar of pictures? Send in a quarter and 25 things to draw, and we&amp;rsquo;ll send you a hand drawn calendar.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;An advent calendar of pictures, how exciting! Karl liked to draw, but every drawing took him a long time since he had trouble holding the pencil.&lt;/p&gt;
&lt;img src=&#34;karl-drawing.png&#34; alt=&#34;A drawing of a dog&#39;s paw holding a pencil, drawing a basic stick figure dog.&#34; /&gt;
&lt;p&gt;But he had a ton of good ideas for drawings!  &amp;ldquo;I wonder what this library advent calendar is all about?&amp;rdquo; wondered Karl. He then remembered that his friends Jeff and Sanjay were really &lt;a href=&#34;https://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf&#34;&gt;good at working together and splitting up work&lt;/a&gt;, so he decided to give them a call to see if they knew anything about it.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hello?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Jeff, it&amp;rsquo;s Karl! How are you?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Karl, I&amp;rsquo;m great! Just working on a problem with Sanjay.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Karl!&amp;rdquo;, said Sanjay.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Hi Sanjay!&amp;rdquo;, said Karl. &amp;ldquo;I had a question for you two. I saw that the local library will make you a calendar if you send in things to draw. Do you two know how it works?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Sure!&amp;rdquo;, said Jeff. &amp;ldquo;It&amp;rsquo;s actually very simple. You can send them a list of things to draw, one for each day, and then they&amp;rsquo;ll have people draw them and send you back a calendar made up of the drawings.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s so fun!&amp;rdquo; said Karl. &amp;ldquo;So one of the assistant librarians draws all of the pictures for me?&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;sequential-drawing.png&#34; alt=&#34;A drawing of a series of prompts with drawings of the prompts. The arrows connect them in order: Dec. 1 Cat, drawing of a cat, Dec. 2 Dog, drawing of a dog, and so on.&#34; /&gt;
&lt;p&gt;&amp;ldquo;Not quite!&amp;rdquo;, said Sanjay. &amp;ldquo;If one librarian does all of the drawing, they&amp;rsquo;ll have a lot of work to do by themself, even if they&amp;rsquo;re fast at drawing. Instead, they send out the ideas to a lot of different librarians at different branches! That way, each librarian only has to draw one picture, and they can do them all at the same time.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;It&amp;rsquo;s a great idea!&amp;rdquo; said Jeff. &amp;ldquo;That way, the assistant librarian can send out a letter with the day and something to draw to each librarian, who can draw it and send it back. Once the assistant librarian has gotten all of the drawings back from the other branches, they put them in order for the calendar. Once they&amp;rsquo;re in the right order, they paste it all together, put a cover on it, and send it right back to you.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s fun!&amp;rdquo; exclaimed Karl. &amp;ldquo;That sounds like it will be much better than drawing every picture one after another, and I just happened to learn the other day how to send a letter from my friend Andrew, which means it&amp;rsquo;ll be very easy to write to the librarian.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;That&amp;rsquo;s right,&amp;rdquo; said Sanjay.  &amp;ldquo;And since all of the libraries are talking to each other to keep track of which library books are where, it&amp;rsquo;s easy for them to keep track of all of the different drawings. It&amp;rsquo;s like your drawings are the books, and they&amp;rsquo;re asking each other for them. That way, the assistant librarian handles all of the coordination between all of the other branches, and you just have to tell them what to do for each day!&amp;rdquo;&lt;/p&gt;
&lt;img src=&#34;distributed-drawing.png&#34; alt=&#34;A drawing of a house and three libraries, now with each library being sent a prompt and each library sending back a drawing that matches the prompt.&#34; /&gt;
&lt;p&gt;&amp;ldquo;That makes sense!&amp;rdquo; said Karl. &amp;ldquo;Thanks so much!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;No problem Karl! Bye!&amp;rdquo; said Jeff and Sanjay.&lt;/p&gt;
&lt;p&gt;Karl then made a list of 25 things to draw. With his mom&amp;rsquo;s help, he sent out the list (and a quarter) to the assistant librarian. A week later, he got the full calendar! The assistant librarian even included a note apologizing for the delay, explaining that they had to wait a little for the very last one, since the librarian at the Brighton branch draws very slowly.&lt;/p&gt;
&lt;p&gt;Karl sent a picture of the calendar to Jeff and Sanjay.  &amp;ldquo;Looks good to me!&amp;rdquo;, they said.&lt;/p&gt;
&lt;p&gt;Karl put the calendar right next to his bed so he could see it when he went to sleep.&lt;/p&gt;
&lt;img src=&#34;karl-sleeping.png&#34; alt=&#34;A drawing of Karl sleeping next to his advent calendar.&#34; /&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;&lt;em&gt;Editor&amp;rsquo;s note: &lt;a href=&#34;real-karl.jpg&#34;&gt;this photo was also found with the document&lt;/a&gt; with a sticky note attached that said &amp;ldquo;dog tax&amp;rdquo;.&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Completing a Part-Time Master&#39;s in Computer Science While Working</title>
      <link>https://alexanderell.is/posts/mscs/</link>
      <pubDate>Sat, 08 Jan 2022 17:33:34 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/mscs/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;I&amp;rsquo;m going to be 29 anyways, so I might as well be 29 with a Master&amp;rsquo;s.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;— me 5 years ago debating whether I should take classes&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On June 5, 2017, I started taking classes at Tufts University while working full time. On January 4, 2022, I submitted the final draft of my Master&amp;rsquo;s thesis. Much like my past self had hoped, I&amp;rsquo;ve made it 4 years in the future, and I&amp;rsquo;m done with my Master&amp;rsquo;s program. At the same time, I&amp;rsquo;m both very happy I did it and very happy to be done, and I&amp;rsquo;m going to describe a few thoughts about my experience.&lt;/p&gt;
&lt;p&gt;For a quick visualization, this is what my work and school looked like for the last few years:&lt;/p&gt;
&lt;img src=&#34;vis.png&#34; alt=&#34;A visualization of my last five years as a char with time extending downwards. On the work side, my work goes from tiny company -&gt; small startup -&gt; Google, while on the school side, my classes are listed.&#34; /&gt;
&lt;p&gt;(Fall 2020 was a busy time for me)&lt;/p&gt;
&lt;p&gt;For a little background, I didn&amp;rsquo;t do any programming in college (though in retrospect I wish I had tried it), instead studying physics, French, and political science. I did a lot of self-studying on my own from roughly January 2015 onwards.&lt;/p&gt;
&lt;h1 id=&#34;by-the-numbers&#34;&gt;By the numbers&lt;/h1&gt;
&lt;h2 id=&#34;classes&#34;&gt;Classes&lt;/h2&gt;
&lt;p&gt;Overall I took 11 courses, 10 at Tufts and 1 at Oregon State University (online), and completed 2 semesters of a Master&amp;rsquo;s thesis. In rough order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data Structures&lt;/li&gt;
&lt;li&gt;Discrete Math&lt;/li&gt;
&lt;li&gt;Algorithms&lt;/li&gt;
&lt;li&gt;Artificial Intelligence&lt;/li&gt;
&lt;li&gt;Theory of Computation&lt;/li&gt;
&lt;li&gt;Machine Architecture &amp;amp; Assembly (Oregon State)&lt;/li&gt;
&lt;li&gt;Networks&lt;/li&gt;
&lt;li&gt;Introduction to Machine Learning&lt;/li&gt;
&lt;li&gt;Cloud Computing&lt;/li&gt;
&lt;li&gt;Compilers&lt;/li&gt;
&lt;li&gt;Debugging Cloud Computing&lt;/li&gt;
&lt;li&gt;Thesis (Summer)&lt;/li&gt;
&lt;li&gt;Thesis (Fall)&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;The first five classes counted towards Tufts&amp;rsquo; Post-Baccalaureate certificate program, which is roughly the equivalent of a CS minor, and the the second grouping of classes (and a few of the Post-Bacc classes) counted towards the Master&amp;rsquo;s degree. I took the assembly class at Oregon State because the Tufts equivalent was 10x as expensive (see later) and wouldn&amp;rsquo;t count towards graduate credit, and I could make up that requirement by taking a class that had the assembly class as a prerequisite. I took a small break from classes in Spring and Summer 2019 after finishing the Post-Bacc, then decided to complete the Master&amp;rsquo;s. I took another small break in the summer of 2020 while I co-hosted interns on my team at work.&lt;/p&gt;
&lt;h2 id=&#34;time&#34;&gt;Time&lt;/h2&gt;
&lt;p&gt;It took me a little over 4 years to do the Tufts CS Post-Bacc and the Master&amp;rsquo;s, at an average rate of 1 class per semester/summer.&lt;/p&gt;
&lt;p&gt;I took these classes while working full time. I transitioned from a tiny company to a very small startup a week after starting classes (June 2017), and I went from that startup to Google in September 2018. I ended up getting a promotion in Fall 2020, and I transitioned from the Hotels team to the Cloud Networking team in April 2021.&lt;/p&gt;
&lt;p&gt;In spring 2020, both my work and classes transitioned to be fully remote, and both have been remote since (with a small window of a few in-person meetings in late 2021).&lt;/p&gt;
&lt;h2 id=&#34;cost&#34;&gt;Cost&lt;/h2&gt;
&lt;p&gt;When I started the classes, tuition was a whopping $5,052 per class for part-time students. After my first 4 classes, the department switched from per-class tuition ($5,052 per class) to per-credit hour tuition ($1,700 per credit). Classes ranged from 3-5 credits, and luckily I was able to finish the program with only 3-credit classes after that.&lt;/p&gt;
&lt;p&gt;Google has an education tuition reimbursement benefit for classes taken while working there, and I was able to use this benefit for all classes I took after starting at Google.&lt;/p&gt;
&lt;p&gt;Total cost: ~$61,000 &lt;br&gt;
Google&amp;rsquo;s help: ~$23,000 &lt;br&gt;
My final cost: ~$38,000 &lt;br&gt;&lt;/p&gt;
&lt;h1 id=&#34;thoughts-feelings-and-reactions&#34;&gt;Thoughts, feelings, and reactions&lt;/h1&gt;
&lt;h2 id=&#34;the-good&#34;&gt;The Good&lt;/h2&gt;
&lt;h3 id=&#34;classes-really-helped-me-in-my-cs-career&#34;&gt;Classes really helped me in my CS career&lt;/h3&gt;
&lt;p&gt;Even though there&amp;rsquo;s a gap between academic CS and working in tech, the classes I took had a very positive influence on my career with computers.&lt;/p&gt;
&lt;p&gt;When I got my first real programming job at the small startup, they hired me as an inexperienced junior frontend engineer who would be willing to learn fast, and the classes helped show that I was serious about learning more. This helped me transition away from doing some development work at the tiny non-tech company to being a Real Engineer at a real (albeit small) tech company. That alone was easily worth the price of admission.&lt;/p&gt;
&lt;p&gt;I took the Algorithms and AI courses before interviewing at Google, and the in-depth coverage of various algorithms helped build the fundamentals I used in my interviews and on the job since. I had done algorithm interviews before the algo class, and I &lt;em&gt;highly&lt;/em&gt; recommend taking the class first. (Warning: confirmation bias) I didn&amp;rsquo;t go in with a bunch of LeetCode problems memorized, but instead a pretty good foundation of algorithms, data structures, and different types of problems I understood after carefully working through them, and these classes were very helpful for building that understanding. Again, easily worth it.&lt;/p&gt;
&lt;p&gt;The other way these classes helped me is my transition at work from working on front end work to more backend and distributed systems work. When I started at Google, I worked mostly on the front end, but slowly over time I transitioned deeper into the stack, eventually transitioning teams to work on the Cloud Networking team on load balancing, deep in the C++ and networking stack. The classes I took on networking, distributed systems, and debugging distributed systems were incredibly helpful for building my background knowledge and helping me find an area I was really interested in, and I even did my master&amp;rsquo;s thesis on an area tangentially related to my day job work (adding a new flavor of distributed tracing to Envoy, an open source proxy that we use at work). These classes made this transition easier, and I think there&amp;rsquo;s also some value in the narrative around them too, being able to point to them in conversations as evidence of my interest in learning about this new area.&lt;/p&gt;
&lt;h3 id=&#34;shining-light-on-the-mysterious-caves-of-cs&#34;&gt;Shining light on the mysterious caves of CS&lt;/h3&gt;
&lt;p&gt;Interviewing and getting jobs aside, one of the best benefits of my classes is that they helped shed light on a wide selection of various areas in computer science. This served two great purposes: helping demystify areas that initially seemed scary and showing the true depth of some of these areas.&lt;/p&gt;
&lt;p&gt;Coming into computer work without a solid CS background meant that there were many gaps in my knowledge. I had taken a few online classes and done some reading, but there were still many areas that I knew of without knowing how they worked. These can be described as the known unknowns; I&amp;rsquo;ve heard of it, and I know I don&amp;rsquo;t know about it. For example, there were many things in networking I had known about from cursory reading; I roughly knew about TCP/IP (partly from seeing the option in StarCraft when connecting to Battlenet in 2001), but I wasn&amp;rsquo;t familiar with any of the details. This is where the classes were especially helpful, as they represented a guided and structured exploration into these areas.&lt;/p&gt;
&lt;p&gt;From networking to algorithms, the in-depth view helped to shed light on the mysterious unknowns, and it made these areas much more approachable. It helped build my portfolio of solved problems, building up my tool chest (or treasure chest?) of problems I had solved in the past, helping to &lt;a href=&#34;https://alexanderell.is/posts/trust-in-your-unconscious/&#34;&gt;further convince me that my future problems are solvable too&lt;/a&gt;. Having built a basic compiler, it wasn&amp;rsquo;t nearly as scary any more, and it helped show me that I could work hard at an area to understand it.&lt;/p&gt;
&lt;p&gt;The other side of this equation is that having seen the basics in introductory classes, I got a better appreciation for the absolute depth of complexity of some of the areas. These can be described as the previously unknown unknowns; I now have a greater appreciation for just how much I don&amp;rsquo;t know. Like the small specialty in the classic &lt;a href=&#34;https://matt.might.net/articles/phd-school-in-pictures/&#34;&gt;illustrated guide to a PhD&lt;/a&gt;, these classes helped me see some of the depth to the edge, and that distance is still vast for me for many, many subfields in CS. It makes me very grateful that we have smart people working on these problems!&lt;/p&gt;
&lt;h3 id=&#34;learning-more-about-cs-academia&#34;&gt;Learning more about CS academia&lt;/h3&gt;
&lt;p&gt;Another solid benefit from my Master&amp;rsquo;s was being able to get a better picture of research, academia, and CS academia. I didn&amp;rsquo;t do any research in undergrad, and when deciding on doing the Master&amp;rsquo;s after the initial 5-class Post-Bacc, I wanted to get a little exposure to the academic side to see if it was something I liked.&lt;/p&gt;
&lt;p&gt;While doing the program, I had the opportunity to work in the lab of one of my professors, who ended up being my advisor for my MS thesis. I was able to help out on a published research project (and &lt;a href=&#34;https://www.csauthors.net/distance/paul-erdos/alex-ellis&#34;&gt;get a very casual Erdős number of 4&lt;/a&gt;). I also had the option of doing the pure 10-class Master&amp;rsquo;s or doing a thesis at the end, and I chose to do the thesis to see what it was like. I ended up doing my thesis on the perfect intersection between work (Envoy, a distributed proxy), my advisor&amp;rsquo;s work (distributed tracing), and open source (all open source, so much easier to write about with work&amp;rsquo;s approval). This was another reason I chose to do an in-person program; although there are many cheaper online-only options, I had read that getting involved in research wasn&amp;rsquo;t as much of an option for those.&lt;/p&gt;
&lt;p&gt;This was all hard to do while working full time, and nearly every time I left a class or a research meeting, all I could think about was how nice it would be to do it full time without the additional distraction of my 9-5. I really enjoyed the collaborative and explorative aspects of the research, and there was a feeling of doing something new for the first time that I greatly liked. The classes and the research work also really scratched my itch to learn, which was very satisfying. I think it was especially helpful when I was working on front end work for my job, as I started to enjoy (and understand!) distributed systems, which was separated from what I was doing for my day-to-day. I&amp;rsquo;ve since moved into an area where I do that for my day job too, and I find that itch scratched by my 9-5 work as well.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve thought about continuing with the PhD (Tufts even has a part-time PhD program), but I&amp;rsquo;m not going to do so right now. I&amp;rsquo;m going to take some time to have a little break outside of my day job, and my day job is giving me plenty to learn and keep my learning-gremlin happy. Someday I may taper down to part-time at work and go back to school, but I&amp;rsquo;m going to enjoy my free time for a little bit. One other barrier to doing the PhD full time is financial; my big tech pay check allows me to keep up my expensive cheese habits, and things would be very different on the grad student stipend. I&amp;rsquo;ve heard that one of the reasons to go straight from undergrad to grad school is that you don&amp;rsquo;t get tempted by that sweet taste of tech salary, and for better or for worse, I&amp;rsquo;ve both tasted it and gotten used to it.&lt;/p&gt;
&lt;h3 id=&#34;structure&#34;&gt;Structure&lt;/h3&gt;
&lt;p&gt;Learning on your own is hard. I&amp;rsquo;ve started many more online classes than I&amp;rsquo;ve finished, and although I would love to imagine myself as the diligent autodidact who can sit down and hammer out an online class on a whim, realistically it&amp;rsquo;s really hard to do.&lt;/p&gt;
&lt;p&gt;Because so much of learning is about consistently showing up and doing the work, I&amp;rsquo;ve found that it&amp;rsquo;s easiest for me to do so with some amount of structure, deadlines, and accountability. I think that&amp;rsquo;s why learning for my day job is pretty doable for me, as all three are built in (and I often need to do so to support my cheese habit). But this is where the in-person classes shined; there was clear structure with a built-in physical context of being at school to learn, clear deadlines, and built-in accountability with professors I got to know. There was also the cost aspect — having paid for it, I was much more motivated to do it to the best of my ability. I find it&amp;rsquo;s important to harness the fallacies that fool your brain!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve taken (or tried to take) online classes for free with various levels of structure to various levels of success. Classes like CS50 with solid structure and clear assignments were doable, though they took me much longer than a semester. Classes like MIT&amp;rsquo;s Intro to Algorithms was much harder, and I only ever got about halfway through. I&amp;rsquo;ve also taken a single online class that I paid proper tuition for, and this was the assembly class at Oregon State University. I also did many in-person classes that I paid (a lot) for. I found the in-person classes to be the best fit for how my brain works, and I&amp;rsquo;ve found that I do my best when it&amp;rsquo;s live, I can raise my hand and ask questions in class, and I build connections with my professor and peers. A non-trivial part of it is also going through it with fellow students, which was a great aspect.&lt;/p&gt;
&lt;h3 id=&#34;how-to-learn&#34;&gt;How to learn&lt;/h3&gt;
&lt;p&gt;One of the odd benefits from taking classes while working full time was that it strongly encouraged me to develop better study and learning habits.&lt;/p&gt;
&lt;p&gt;Those habits and skills were not the best during my undergrad studies, which included a lot of skipped readings, procrastination, and many last minute cramming finishes, often resulting in all nighters the night before a deadline or exam. During my recent studies, it took exactly one all nighter the night before a work day to realize that it wasn&amp;rsquo;t a tangible option long term, as I had to zombie my way through a long day of meetings and coding at far less than 100%.&lt;/p&gt;
&lt;p&gt;Combined with a little more maturity and interest in the subject matter, I focused on building better study habits and skills. I owe a lot to the &lt;em&gt;Learning How To Learn&lt;/em&gt; class and the accompanying &lt;em&gt;A Mind For Numbers&lt;/em&gt; book, as they helped me build a very strong foundation with practical advice and knowledge about how learning works. It turns out that doing the reading, starting early on assignments, and studying consistently really does help.&lt;/p&gt;
&lt;p&gt;I was able to finish all of my classes well and efficiently, getting better grades than I ever did in my undergrad without any additional all night study sessions. There were a few late nights, but they were generally few and far between. Overall it gave me a better appreciation for healthy learning habits that I&amp;rsquo;ll keep close moving forward. In particular, I&amp;rsquo;ll continue to &lt;a href=&#34;posts/trust-in-your-unconscious/&#34;&gt;go for long walks after a long session of research or any time I&amp;rsquo;m particularly stuck on a problem&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-not-so-good&#34;&gt;The Not So Good&lt;/h2&gt;
&lt;h3 id=&#34;where-did-my-money-go&#34;&gt;Where did my money go?&lt;/h3&gt;
&lt;p&gt;As noted above, my final cost for this was nearly $40,000. The Tufts classes were very expensive, at roughly $5,000 per class. Halfway through my Post-Bacc (the initial 5 classes), Tufts changed part-time tuition to be per-credit instead of per-class, with each class being 3-5 credits. I had actually planned to take Tufts&amp;rsquo; assembly class, but at 5 credits, it would have been even pricier at $8,500+. I ended up not taking the class at Tufts, instead getting the background knowledge via Oregon State University&amp;rsquo;s Assembly class and making up the Master&amp;rsquo;s Assembly requirement with a grad-level class that had an Assembly class pre-req.&lt;/p&gt;
&lt;p&gt;There were a few other small tuition hacks that helped. First, I took the maximum number of classes in the Post-Bacc that would count towards the MS (3 classes). I also opted out of signing up for an additional credit for the more advanced version of a class, though I ended up doing the advanced work anyways. While nominally the credit was supposed to represent the extra work the student had to go through for the more advanced work, for part-time students like myself it would mean an extra $1,700 in tuition, and luckily I didn&amp;rsquo;t need the credit.&lt;/p&gt;
&lt;p&gt;Small one-off optimizations like this helped reduce the price tag by $5-10k, which was absolutely fine with me.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve worked very hard, but I&amp;rsquo;m also very fortunate to be in a position where this tuition was manageable. I spent many months at the tiny company saving up for the first class, and I was very fortunate to have a few job and pay changes that made the tuitions even more palatable as I went. Ironically, my Google job came with the biggest pay increase, but with the tuition reimbursement, it also meant that the overall cost was greatly reduced.&lt;/p&gt;
&lt;p&gt;With the very positive impact on my day job(s), the benefit greatly outweighs the cost for me, even without considering the many other intangible benefits.&lt;/p&gt;
&lt;h3 id=&#34;where-did-my-time-go&#34;&gt;Where did my time go?&lt;/h3&gt;
&lt;p&gt;Taking classes while working full time led to a &lt;em&gt;very&lt;/em&gt; busy schedule. A typical week day for me (pre-pandemic) would be me getting into work around 9, working for a few hours, commuting to class, going to a class (or two), commuting back to the office, and working late to make up the missed time. Add in the commute to and from the office and it got very busy very quickly, especially with projects, homeworks, readings, and group meetings.&lt;/p&gt;
&lt;p&gt;Fortunately, my wife was similarly busy with graduate school for the first three years of my studies, and I was able to go through it with a buddy. We would go to the library together, work from a coffee shop, and even have little study halls at home. The solidarity was a huge help.&lt;/p&gt;
&lt;p&gt;The busy schedule did mean that a lot of my non-day-job time was spent studying or working. I have a wide variety of interests and hobbies, and many of them have been neglected more than I would have liked in favor of school work. This is another reason I&amp;rsquo;m not immediately jumping into a 5-year part-time PhD program; I&amp;rsquo;d like to find a little more balance in my life.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d also like to find a little more balance at my job. It&amp;rsquo;s really nice to start work at 10 and go home at 5 or 6, but with the many interruptions during the day due to school, I would frequently be shifting my hours and working later to make up the time. Context switches are expensive, and I&amp;rsquo;m very much looking forward to fewer of them.&lt;/p&gt;
&lt;p&gt;As an aside, the busy back and forth also meant that I was less likely to stick around for office hours after class or student group meetings, as I wanted to go finish my work for the day so I could go home. I think this also contributed to some feelings of disconnected &amp;ldquo;He doesn&amp;rsquo;t even go here&amp;rdquo; vibes as a part-time student.&lt;/p&gt;
&lt;h2 id=&#34;the-so-so&#34;&gt;The So-So&lt;/h2&gt;
&lt;h3 id=&#34;where-did-my-in-person-interactions-go&#34;&gt;Where did my in-person interactions go&lt;/h3&gt;
&lt;p&gt;For a number of reasons, I chose to do an in-person MS instead of doing an online program (such as Georgia Tech&amp;rsquo;s MSCS, if I could get in). I knew it would be greatly more expensive, but I was hoping it would allow for the benefits of in-person classes, such as meeting peers and professors, potential opportunities to get involved in research, and the benefit of a physical context dedicated to learning.&lt;/p&gt;
&lt;p&gt;While that all went pretty well for the first few years, everything went fully remote in the spring of 2020. All of a sudden, everything was at home over video chat. The transition was hard for both work and school, and even though I was able to do my classes, ask questions in class, and get involved with research, it was very different. It was missing a lot of what I enjoyed from going in-person. Furthermore, I think it contributed to my feelings of burnout and zoom fatigue, as I was now spending both work time and school time on video chats, and without the physical context switches of going to work and going to school, I was then attempting (and failing) to balance home, work, and life from the same living room. This was a difficult period for me, and the busy schedule didn&amp;rsquo;t help.&lt;/p&gt;
&lt;p&gt;That being said, there were two positives. First, I didn&amp;rsquo;t have a long commute anywhere any more, so I had a bit of time saved throughout the day, though this led to even faster context switches as I would have to jump from one video chat to another. The other large benefit was that during a time where it felt like everything had stopped moving forward and time stood still, I still had tangible forward progress towards my academic goals. The structure of the classes meant that I was moving forward in part of my life, though the rest of it was standing still. With that feeling of &amp;ldquo;let&amp;rsquo;s keep moving forward&amp;rdquo;, I kept up a pretty good pace, and as I got better at doing everything from home, I was able to strike a better balance and take on more amorphous and ambiguous work (such as getting involved with research and working on the thesis).&lt;/p&gt;
&lt;h2 id=&#34;was-it-worth-it&#34;&gt;Was it worth it?&lt;/h2&gt;
&lt;p&gt;To me, taking the classes and completing the MS was absolutely worth it, and I&amp;rsquo;m very glad I saw it through to completion. At the same time, I&amp;rsquo;m very happy to be done. I&amp;rsquo;m looking forward to spending more time writing, drawing, playing music, cooking, and making puppets. There may be more classes later on in my future (there&amp;rsquo;s that learning itch again), but for now, I&amp;rsquo;m going to enjoy a little more balance and a little more free time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Strange Path of Accepting How Your Brain Works</title>
      <link>https://alexanderell.is/posts/limitations/</link>
      <pubDate>Sun, 31 Oct 2021 00:10:11 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/limitations/</guid>
      <description>&lt;p&gt;I recently read &lt;em&gt;Turing&amp;rsquo;s Cathedral&lt;/em&gt;, a book about the early history of computers, and it largely focused on the life and brilliant times of John von Neumann, who was, to put it simply, &lt;a href=&#34;https://en.wikipedia.org/wiki/John_von_Neumann#Cognitive_abilities&#34;&gt;a genius&lt;/a&gt;. It is both fascinating and disappointing to learn about a brain that works so much better than your own.&lt;/p&gt;
&lt;p&gt;Fascinating because the wide spectrum of human ability has led to a brain so capable. That we (in the general, humanity sense of &amp;ldquo;we&amp;rdquo;) are able to perform such feats is incredible. Genius exists, living in one of our heads and manifesting its abilities through photographic memory, mastery of over half a dozen languages, and imaginative and original creative work.&lt;/p&gt;
&lt;p&gt;But, to be perfectly honest, there&amp;rsquo;s something somewhat disappointing too, especially if your mind is prone to drawing comparisons with itself.  Reading about John von Neumann&amp;rsquo;s photographic memory while having trouble remembering a birthday is unsatisfying, as is reading about him needing only 4 hours of sleep while feeling the tiring effects of only getting 7 hours yourself. It is humbling, if only by giving perspective into the actual width of the ability gap. How nice it would be to have all of that!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Before getting into tech, I worked as a tutor for high school students. One student I worked with on his Algebra 2 work wasn&amp;rsquo;t always the best at completing his assigned work. We ended up striking a deal where if he completed his worksheets, I would watch his favorite anime series or movie and we could spend a small part of every weekly tutoring session talking about it. In a win-win, he ended up completing more assignments, and I ended up watching a few extra shows and movies in my free time.&lt;/p&gt;
&lt;p&gt;One of the movies I ended up watching was an anime about a high school math genius who had to help defend a virtual world against an AI that had turned evil. At one point near the end of the film, the student is hacking away at defeating the AI, but the AI keeps blocking his digital path with complicated cryptography problems. The protagonist would take a few minutes of wild scribbling as he solved the equations by hand on a stack of scratch paper, typing in the solution to unblock his digital path and continue his attempt at saving the day.&lt;/p&gt;
&lt;p&gt;Just when time is running out, the evil AI throws one more problem at the student, even more complicated than the last, but there isn&amp;rsquo;t enough time to solve it by hand.  The kid seems to unfocus his eyes as he transfixes on the problem, sweating from the stress, and drops his pencil as he fully absorbs himself in the problem. After a moment, he is miraculously able to complete the problem entirely in his head, and he types in the correct answer, letting him enter a few final keystrokes to finally help save the day at the last moment.&lt;/p&gt;
&lt;!-- Summer War, timestamp 1:42:57 (https://gogoanime2.org/watch/summer-wars/1) --&gt;
&lt;p&gt;Back in real life a little while after watching the film, I was working with my real student on a math assignment. We had talked about some strategies for solving a certain type of problem, and I had encouraged him to carefully write out each step, explaining that it&amp;rsquo;s one of the best ways to make sure you&amp;rsquo;re taking your time to not miss anything and that it would help him (and his teacher) see and confirm his process and understanding.&lt;/p&gt;
&lt;p&gt;A few problems in, I noticed him pause when starting on a new problem, and after a brief moment, he wrote down an answer without any intermediate steps or work. I could see that he was skipping the process of writing down the steps we had talked about, trying to work through it entirely in his head. It was almost as if he was his favorite character, faced with the complicated problem, but after staring at it and absorbing it he could arrive at a complete solution entirely in his mind.&lt;/p&gt;
&lt;p&gt;Part of the reason I could tell he was trying to take the shortcut was because he had missed a step he had previously gotten by writing out the steps. I&amp;rsquo;m not &amp;ldquo;anti mental math&amp;rdquo;; more that I&amp;rsquo;m very &amp;ldquo;pro practicing and building the understanding and fundamentals&amp;rdquo;! I encouraged him to work through the steps for the problem, and he ended up finding his mistake after writing out the process.&lt;/p&gt;
&lt;p&gt;Having seen the anime, I later wondered if he was playing as if he was the protagonist, and I remember smiling at the thought; I too wish I could just do it all in my head! As I think back, it&amp;rsquo;s a very human thing to do, and they&amp;rsquo;re very natural thoughts to think.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;What if I was able to do complicated math problems in my head on a whim? What if I could keep track of every tricky step in a convoluted algorithm solely in my mind? How nice would that be?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;What if I had the memory of von Neumann?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I don&amp;rsquo;t think it happened at any specific moment, and maybe it&amp;rsquo;s a part of growing up, but I&amp;rsquo;ve come to accept the realities of what my brain can and cannot do.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s funny to write that out even now, because it seems at the same time both obvious and final. Of course I don&amp;rsquo;t have photographic memory, as much as I&amp;rsquo;d love to imagine I did. How wonderful it would be to not have to write out my work! But I know that I can&amp;rsquo;t keep it all in my head. It would be delightful to have perfect pitch, but I have to find my 440hz with a tuning fork just like everyone else.&lt;/p&gt;
&lt;p&gt;At the same time, there&amp;rsquo;s a certain finality to it. How fun it is to imagine! Can you imagine what you would do if you could remember every page you&amp;rsquo;ve ever read after a single reading? I&amp;rsquo;ve thought about it before. How much easier school would be, or remembering peoples&amp;rsquo; names, or those tricky French verb tenses I always trip up on, or&amp;hellip;&lt;/p&gt;
&lt;p&gt;But, I can&amp;rsquo;t. Somehow they slip easily through my mind, maybe catching on and sticking in a neural pathway somewhere, but maybe not. &lt;em&gt;Il faut que tu&amp;hellip; soie? sois? soies??&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;More importantly though, it&amp;rsquo;s OK that my memory isn&amp;rsquo;t perfect, and I know that about my brain. It is what it is.&lt;/p&gt;
&lt;p&gt;I think the most powerful thing about acknowledging your brain&amp;rsquo;s limitations is 1) the acceptance of it and 2) your ability to work with your brain with those limitations in mind.  I don&amp;rsquo;t have perfect memory, but I can learn about how memory works and leverage tools to help me move past what my brain can do on its own. It&amp;rsquo;s why I find checklists, mnemonics, and spaced repetition so helpful, as they let me augment what I&amp;rsquo;m working with. We&amp;rsquo;re lucky to have enough tools at our disposal (and the ability to build our own tools), and much like Steve Jobs&amp;rsquo; metaphor of the computer as a bicycle for the mind, once you know how to work with your brain, you can help it to go even further.&lt;/p&gt;
&lt;p&gt;As much as I tease AI startup landing pages for listing &amp;ldquo;AI + Human&amp;rdquo;, I think that&amp;rsquo;s one area where the combination of humans and computers does especially well. For me, there are certain things that a computer helps with, such as keeping track of some detail I&amp;rsquo;ll need in the future or letting me know when 30 minutes has passed. It can handle the specifics of the spaced repetition algorithm, and all I have to do is tell it how good I felt about the card I just got correct. That being said, I don&amp;rsquo;t think computers are the fix for everything, at least for my own lizard brain. I know I&amp;rsquo;m a sucker for infinite scroll and interesting content, and since I know I do best after at least 8 hours of sleep, part of the battle is even to get off the damn computer!&lt;/p&gt;
&lt;p&gt;So part of the good news is that we can work with tools to augment our own abilities to get closer to what some of these gifted brains can do from just their natural ability. Maybe it&amp;rsquo;s not fair that they get it by default, but it&amp;rsquo;s not worth it to worry too much about &amp;ldquo;fair&amp;rdquo;. The people we look up to feel the same way about people they look up to too, and if Nobel Prize winners look up to &lt;a href=&#34;https://en.wikipedia.org/wiki/The_Martians_(scientists)&#34;&gt;the Martians&lt;/a&gt; as otherworldly, that&amp;rsquo;s enough to make me feel better already.&lt;/p&gt;
&lt;p&gt;The other good news is that a lot of what looks like natural ability is the result of a great deal of hard work and practice. As easy as it is to discount a skill gap as due to some ingrained natural aptitude, the reality is that they&amp;rsquo;ve probably worked at it a lot more than I have. There&amp;rsquo;s a vignette I really enjoy from Richard Hamming&amp;rsquo;s &lt;em&gt;You and Your Research&lt;/em&gt; talk, where he talks about drive:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I worked for ten years with John Tukey at Bell Labs. He had tremendous drive. One day about three or four years after I joined, I discovered that John Tukey was slightly younger than I was. John was a genius and I clearly was not. Well I went storming into Bode&amp;rsquo;s office and said, &amp;ldquo;How can anybody my age know as much as John Tukey does?&amp;rsquo;&amp;rsquo; He leaned back in his chair, put his hands behind his head, grinned slightly, and said, &amp;ldquo;You would be surprised Hamming, how much you would know if you worked as hard as he did that many years.&amp;rdquo; I simply slunk out of the office!&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So the good news is twofold: you can work with your brain, and many of those you admire for having natural talent are probably building their abilities by putting in a great deal of work.&lt;/p&gt;
&lt;p&gt;As a quick aside, this seems like a good point to note the difference between putting in the work and day-job work or &amp;ldquo;hustle culture&amp;rdquo;, in that I strongly reject the idea that you should always be working harder and harder for money in every facet of your life. It&amp;rsquo;s wonderful to have a hobby, but if that hobby is basket weaving and you want to get better at basket weaving, it&amp;rsquo;s OK to want to work on getting better at it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;To stave off the risk of rambling, I&amp;rsquo;m going to cut it here. Knowing and accepting your brain is half the battle, and working with it is even better.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to go review a few French flashcards.&lt;/p&gt;
&lt;br&gt;
&lt;hr&gt;
&lt;p&gt;Misc. recommended reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;John von Neumann&amp;rsquo;s &lt;a href=&#34;https://en.wikipedia.org/wiki/John_von_Neumann&#34;&gt;Wikipedia article&lt;/a&gt; (but don&amp;rsquo;t get too down on yourself)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/The_Martians_(scientists)&#34;&gt;Wikipedia article about the Hungarian Martians&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Richard Hamming&amp;rsquo;s &lt;a href=&#34;https://www.cs.virginia.edu/~robins/YouAndYourResearch.html&#34;&gt;&lt;em&gt;You and Your Research&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://books.google.com/books/about/Turing_s_Cathedral.html?id=XyzstMs30GYC&amp;amp;source=kp_book_description&#34;&gt;&lt;em&gt;Turing&amp;rsquo;s Cathedral&lt;/em&gt;&lt;/a&gt; by George Dyson&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Writing an RPC From Scratch</title>
      <link>https://alexanderell.is/posts/rpc-from-scratch/</link>
      <pubDate>Sun, 18 Apr 2021 13:15:13 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/rpc-from-scratch/</guid>
      <description>&lt;p&gt;There are many ways for computers to talk to other computers. A commonly used approach is a Remote Procedure Call, or an RPC. An RPC allows for the abstraction of calling another computer&amp;rsquo;s procedure as if it were a local one with all of the transmission and communication taken care of.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we&amp;rsquo;re writing some math program on a single computer and we have some procedure or function that handles checking if a number is prime. We can use this function as we&amp;rsquo;d like, passing it numbers and getting an answer, and it can live in our computer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;is-prime-as-library.png&#34; alt=&#34;The is_prime function as a library&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is fine, and for doing many things, it&amp;rsquo;s very helpful to keep things nearby. It&amp;rsquo;s easy to call, and since it&amp;rsquo;s right alongside the rest of our code, there&amp;rsquo;s nearly no delay when we have to send it numbers.&lt;/p&gt;
&lt;p&gt;But, there are cases when it isn&amp;rsquo;t as helpful to keep it local. Maybe we want to have it run on a really powerful computer with many cores and lots of memory so it can check really, really big numbers. Well, that&amp;rsquo;s not so bad. We can run our main program on this big computer as well, so even though the rest of the program might not need it, the prime finder can use as much of the computer as it can.  What if we want to let other programs reuse the prime finder? We could make it a library that we can share between programs, but we might need a lot of memory resources for every computer that runs the prime finding library.&lt;/p&gt;
&lt;p&gt;What if we just ran the prime function on a separate computer, then talked to that computer when we needed to check a number? That way, we can beef up this prime finder computer, and we can share it with other programs running on other computers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;is-prime-on-big-computer.png&#34; alt=&#34;The is_prime function living on a powerful computer as part of a distributed network &#34;&gt;&lt;/p&gt;
&lt;p&gt;The downside here is more complexity. Computers fail, networks are unreliable, and we have to worry about sending the answer back and forth. If you&amp;rsquo;re just writing a math program, you may not want to worry about network details, resending lost packets, or even how to find the computer that&amp;rsquo;s running the prime finder. If your job is writing the best prime-finding program, you may not want to worry about how to listen for requests or check for closed sockets.&lt;/p&gt;
&lt;p&gt;This is where remote procedure calls come into play. We can wrap the complexity of inter-computer communication in the center, and we can surface a simple interface, called a &lt;em&gt;stub&lt;/em&gt;, to either side of the conversation. To the math-program writer, we can make it look like you&amp;rsquo;re just calling the function that lives on the other computer. To the prime-finding program writer, we can just make it look like your function is being called. If we abstract away the middle, each side can focus on their respective details while still being able to enjoy the benefit of splitting this computation across multiple computers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;is-prime-distributed-with-stubs.png&#34; alt=&#34;The is_prime function living on a powerful computer as part of a distributed network &#34;&gt;&lt;/p&gt;
&lt;p&gt;The main job of an RPC call is to handle this middle section. Part of it has to live on the math program computer, where it has to take the arguments, package them, and send them to the other computer. When it gets a response, it has to unpackage it and pass it back. The part that lives on the prime finder computer has to wait for requests, unpackage the arguments, pass them to the function, get the result, package it, and send it back to whoever asked for it. Importantly, both the math program and the prime finder program have a clean interface between them and their respective stubs that allows for this abstraction.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;RPC-diagram.png&#34; alt=&#34;RPC components.&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;Diagram from Implementing Remote Procedure Calls, Birrell &amp; Nelson, 1981&lt;/em&gt;
&lt;/center&gt;
&lt;br&gt;
&lt;p&gt;For more details, I&amp;rsquo;d highly recommend the 1981 paper &lt;em&gt;Implementing Remote Procedure Calls&lt;/em&gt; by Andrew D. Birrell and Bruce Jay Nelson&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, where they walk through the use of RPCs at Xerox PARC.&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&#34;writing-an-rpc-from-scratch&#34;&gt;Writing an RPC from scratch&amp;hellip;&lt;/h3&gt;
&lt;p&gt;What would this look like if we tried to write it?&lt;/p&gt;
&lt;p&gt;(With a big old caveat that I&amp;rsquo;m far from an expert in C, but I can scratch enough down with a toy problem to (hopefully) get a concept across. Imagine a big asterisk after nearly every line that says &amp;ldquo;would be different for production code&amp;rdquo;. These examples are very much inspired by Beej&amp;rsquo;s Guide to Network Programming&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.)&lt;/p&gt;
&lt;p&gt;We can start with our basic math program. Maybe for the sake of simplicity, it&amp;rsquo;s a command line tool that takes some input and checks whether or not it&amp;rsquo;s prime. It has a separate method, &lt;code&gt;is_prime&lt;/code&gt;, for the actual checking.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// basic_math_program.c
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdbool.h&amp;gt;

// Basic prime checker. This uses the 6k&amp;#43;-1 optimization
// (see https://en.wikipedia.org/wiki/Primality_test)
bool is_prime(int number) {
  // Check first for 2 or 3
  if (number == 2 || number == 3) {
    return true;
  }
  // Check for 1 or easy modulos
  if (number == 1 || number % 2 == 0 || number % 3 == 0) {
    return false;
  }
  // Now check all the numbers up to sqrt(number)
  int i = 5;
  while (i * i &amp;lt;= number) {
    // If we&amp;#39;ve found something (or something &amp;#43; 2) that divides it evenly, it&amp;#39;s not
    // prime.
    if (number % i == 0 || number % (i &amp;#43; 2) == 0) {
      return false;
    }
    i &amp;#43;= 6;
  }
  return true;
}

int main(void) {
  // Prompt the user to enter a number.
  printf(&amp;#34;Please enter a number: &amp;#34;);
  // Read the user&amp;#39;s number. Assume they&amp;#39;re entering a valid number.
  int input_number;
  scanf(&amp;#34;%d&amp;#34;, &amp;amp;input_number);

  // Check if it&amp;#39;s prime
  if (is_prime(input_number)) {
    printf(&amp;#34;%d is prime\n&amp;#34;, input_number);
  } else {
    printf(&amp;#34;%d is not prime\n&amp;#34;, input_number);
  }

  return 0;
}

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;OK, some potential issues there, and the author clearly didn&amp;rsquo;t handle all edge cases, but maybe they had some pressing deadlines. Either way, it&amp;rsquo;s using this function, and it looks like it&amp;rsquo;s working pretty well:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;basic-math-program.png&#34; alt=&#34;Running basic_math_program.c; says 31 is prime&#34;&gt;&lt;/p&gt;
&lt;p&gt;So far so good. Maybe we want to split this up into different files, so &lt;code&gt;is_prime&lt;/code&gt; would be reusable between programs on the same computer. That&amp;rsquo;s easy enough. Let&amp;rsquo;s start by making a separate library for &lt;code&gt;is_prime&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;pre&gt;&lt;code&gt;
// is_prime.h
#ifndef IS_PRIME_H
#define IS_PRIME_H

#include &amp;lt;stdbool.h&amp;gt;

bool is_prime(int number);

#endif
&lt;/code&gt;&lt;/pre&gt;


&lt;pre&gt;&lt;code&gt;
// is_prime.c
#include &amp;#34;is_prime.h&amp;#34;

// Basic prime checker. This uses the 6k&amp;#43;-1 optimization
// (see https://en.wikipedia.org/wiki/Primality_test)
bool is_prime(int number) {
  // Check first for 2 or 3
  if (number == 2 || number == 3) {
    return true;
  }
  // Check for 1 or easy modulos
  if (number == 1 || number % 2 == 0 || number % 3 == 0) {
    return false;
  }
  // Now check all the numbers up to sqrt(number)
  int i = 5;
  while (i * i &amp;lt;= number) {
    // If we&amp;#39;ve found something (or something &amp;#43; 2) that divides it evenly, it&amp;#39;s not
    // prime.
    if (number % i == 0 || number % (i &amp;#43; 2) == 0) {
      return false;
    }
    i &amp;#43;= 6;
  }
  return true;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/p&gt;
&lt;p&gt;We can now include it and call it from our main program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// basic_math_program_refactored.c
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdbool.h&amp;gt;

#include &amp;#34;is_prime.h&amp;#34;

int main(void) {
  // Prompt the user to enter a number.
  printf(&amp;#34;Please enter a number: &amp;#34;);
  // Read the user&amp;#39;s number. Assume they&amp;#39;re entering a valid number.
  int input_number;
  scanf(&amp;#34;%d&amp;#34;, &amp;amp;input_number);

  // Check if it&amp;#39;s prime
  if (is_prime(input_number)) {
    printf(&amp;#34;%d is prime\n&amp;#34;, input_number);
  } else {
    printf(&amp;#34;%d is not prime\n&amp;#34;, input_number);
  }

  return 0;
}

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Trying it again, it looks like it still works! Though it would have been nice if the original author had written some tests&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;basic-math-program-refactored.png&#34; alt=&#34;Running basic_math_program_refactored.c; still says 31 is prime&#34;&gt;&lt;/p&gt;
&lt;p&gt;This is OK so far, but now we&amp;rsquo;ve come to that junction we mentioned earlier, where we want to distribute this across computers. We need to write the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Caller stub, which has to&amp;hellip;
&lt;ul&gt;
&lt;li&gt;Pack the argument&lt;/li&gt;
&lt;li&gt;Transmit argument&lt;/li&gt;
&lt;li&gt;Receive the result&lt;/li&gt;
&lt;li&gt;Unpack the result&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Callee stub, which has to&amp;hellip;
&lt;ul&gt;
&lt;li&gt;Receive the argument&lt;/li&gt;
&lt;li&gt;Unpack argument&lt;/li&gt;
&lt;li&gt;Call the function&lt;/li&gt;
&lt;li&gt;Pack the result&lt;/li&gt;
&lt;li&gt;Transmit the result&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Our example is a pretty simple one, since we&amp;rsquo;re just packing and sending a single &lt;code&gt;int&lt;/code&gt; as the argument and receiving a single byte as a result. For the caller library, we can pack the data, create a socket, connect to a host (let&amp;rsquo;s assume localhost for now), send the data, wait to receive, unpack, and then return. Here&amp;rsquo;s what the header file looks like for the caller library:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// client/is_prime_rpc_client.h
#ifndef IS_PRIME_RPC_CLIENT_H
#define IS_PRIME_RPC_CLIENT_H

#include &amp;lt;stdbool.h&amp;gt;

bool is_prime_rpc(int number);

#endif
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The astute (or just conscious) reader will notice that the interface is actually the exact same as when it was just the library, and this is the point! The caller doesn&amp;rsquo;t have to worry about anything other than the business logic it&amp;rsquo;s trying to send (but see caveats below). The implementation, on the other hand, is a little more complex:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// client/is_prime_rpc_client.c

#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;errno.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;netdb.h&amp;gt;

#define SERVERPORT &amp;#34;5005&amp;#34;  // The port the server will be listening on.
#define SERVER &amp;#34;localhost&amp;#34;  // Assume localhost for now

#include &amp;#34;is_prime_rpc_client.h&amp;#34;

// Packs an int. We need to convert it from host order to network order.
int pack(int input) {
  return htons(input);
}

// Gets the IPv4 or IPv6 sockaddr.
void *get_in_addr(struct sockaddr *sa) {
  if (sa-&amp;gt;sa_family == AF_INET) {
    return &amp;amp;(((struct sockaddr_in*)sa)-&amp;gt;sin_addr);
  } else {
    return &amp;amp;(((struct sockaddr_in6*)sa)-&amp;gt;sin6_addr);
  }
}

// Gets a socket to connect with.
int get_socket() {
  int sockfd;
  struct addrinfo hints, *server_info, *p;
  int number_of_bytes;

  memset(&amp;amp;hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;  // We want to use TCP to ensure it gets there
  int return_value = getaddrinfo(SERVER, SERVERPORT, &amp;amp;hints, &amp;amp;server_info);
  if (return_value != 0) {
    fprintf(stderr, &amp;#34;getaddrinfo: %s\n&amp;#34;, gai_strerror(return_value));
    exit(1);
  }

  // We end up with a linked-list of addresses, and we want to connect to the
  // first one we can
  for (p = server_info; p != NULL; p = p-&amp;gt;ai_next) {
    // Try to make a socket with this one.
    if ((sockfd = socket(p-&amp;gt;ai_family, p-&amp;gt;ai_socktype, p-&amp;gt;ai_protocol)) == -1) {
      // Something went wrong getting this socket, so we can try the next one.
      perror(&amp;#34;client: socket&amp;#34;);
      continue;
    }
    // Try to connect to that socket.
    if (connect(sockfd, p-&amp;gt;ai_addr, p-&amp;gt;ai_addrlen) == -1) {
      // If something went wrong connecting to this socket, we can close it and
      // move on to the next one.
      close(sockfd);
      perror(&amp;#34;client: connect&amp;#34;);
      continue;
    }

    // If we&amp;#39;ve made it this far, we have a valid socket and can stop iterating
    // through.
    break;
  }

  // If we haven&amp;#39;t gotten a valid sockaddr here, that means we can&amp;#39;t connect.
  if (p == NULL) {
    fprintf(stderr, &amp;#34;client: failed to connect\n&amp;#34;);
    exit(2);
  }

  // Otherwise, we&amp;#39;re good.
  return sockfd;
}

// Client side library for the is_prime RPC.
bool is_prime_rpc(int number) {

  // First, we need to pack the data, ensuring that it&amp;#39;s sent across the
  // network in the right format.
  int packed_number = pack(number);

  // Now, we can grab a socket we can use to connect see how we can connect
  int sockfd = get_socket();

  // Send just the packed number.
  if (send(sockfd, &amp;amp;packed_number, sizeof packed_number, 0) == -1) {
    perror(&amp;#34;send&amp;#34;);
    close(sockfd);
    exit(0);
  }

  // Now, wait to receive the answer.
  int buf[1];  // Just receiving a single byte back that represents a boolean.
  int bytes_received = recv(sockfd, &amp;amp;buf, 1, 0);
  if (bytes_received == -1) {
    perror(&amp;#34;recv&amp;#34;);
    exit(1);
  }

  // Since we just have the one byte, we don&amp;#39;t really need to do anything while
  // unpacking it, since one byte in reverse order is still just a byte.
  bool result = buf[0];

  // All done! Close the socket and return the result.
  close(sockfd);
  return result;
}

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;As mentioned earlier, this client code needs to pack the argument, connect to the server, send the data, receive the data, unpack it, and return it. This is relatively simple for our example, since we just need to ensure the byte order of the number is in the network order.&lt;/p&gt;
&lt;p&gt;Next, we need to run the callee library on the server. It will call the &lt;code&gt;is_prime&lt;/code&gt; library we wrote earlier, which now lives entirely on the server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// server/is_prime_rpc_server.c

#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;errno.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;sys/socket.h&amp;gt;
#include &amp;lt;netinet/in.h&amp;gt;
#include &amp;lt;netdb.h&amp;gt;
#include &amp;lt;arpa/inet.h&amp;gt;
#include &amp;lt;sys/wait.h&amp;gt;
#include &amp;lt;signal.h&amp;gt;

#include &amp;#34;is_prime.h&amp;#34;

#define SERVERPORT &amp;#34;5005&amp;#34;  // The port the server will be listening on.

// Gets the IPv4 or IPv6 sockaddr.
void *get_in_addr(struct sockaddr *sa) {
  if (sa-&amp;gt;sa_family == AF_INET) {
    return &amp;amp;(((struct sockaddr_in*)sa)-&amp;gt;sin_addr);
  } else {
    return &amp;amp;(((struct sockaddr_in6*)sa)-&amp;gt;sin6_addr);
  }
}

// Unpacks an int. We need to convert it from network order to our host order.
int unpack(int packed_input) {
  return ntohs(packed_input);
}

// Gets a socket to listen with.
int get_and_bind_socket() {
  int sockfd;
  struct addrinfo hints, *server_info, *p;
  int number_of_bytes;

  memset(&amp;amp;hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;  // We want to use TCP to ensure it gets there
  hints.ai_flags = AI_PASSIVE;  // Just use the server&amp;#39;s IP.
  int return_value = getaddrinfo(NULL, SERVERPORT, &amp;amp;hints, &amp;amp;server_info);
  if (return_value != 0) {
    fprintf(stderr, &amp;#34;getaddrinfo: %s\n&amp;#34;, gai_strerror(return_value));
    exit(1);
  }

  // We end up with a linked-list of addresses, and we want to connect to the
  // first one we can
  for (p = server_info; p != NULL; p = p-&amp;gt;ai_next) {
    // Try to make a socket with this one.
    if ((sockfd = socket(p-&amp;gt;ai_family, p-&amp;gt;ai_socktype, p-&amp;gt;ai_protocol)) == -1) {
      // Something went wrong getting this socket, so we can try the next one.
      perror(&amp;#34;server: socket&amp;#34;);
      continue;
    }
    // We want to be able to reuse this, so we can set the socket option.
    int yes = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;yes, sizeof(int)) == -1) {
      perror(&amp;#34;setsockopt&amp;#34;);
      exit(1);
    }
    // Try to bind that socket.
    if (bind(sockfd, p-&amp;gt;ai_addr, p-&amp;gt;ai_addrlen) == -1) {
      // If something went wrong binding this socket, we can close it and
      // move on to the next one.
      close(sockfd);
      perror(&amp;#34;server: bind&amp;#34;);
      continue;
    }

    // If we&amp;#39;ve made it this far, we have a valid socket and can stop iterating
    // through.
    break;
  }

  // If we haven&amp;#39;t gotten a valid sockaddr here, that means we can&amp;#39;t connect.
  if (p == NULL) {
    fprintf(stderr, &amp;#34;server: failed to bind\n&amp;#34;);
    exit(2);
  }

  // Otherwise, we&amp;#39;re good.
  return sockfd;
}

int main(void) {

  int sockfd = get_and_bind_socket();

  // We want to listen forever on this socket
  if (listen(sockfd, /*backlog=*/1) == -1) {
    perror(&amp;#34;listen&amp;#34;);
    exit(1);
  }
  printf(&amp;#34;Server waiting for connections.\n&amp;#34;);

  struct sockaddr their_addr;  // Address information of the client
  socklen_t sin_size;
  int new_fd;
  while(1) {

    sin_size = sizeof their_addr;
    new_fd = accept(sockfd, (struct sockaddr *)&amp;amp;their_addr, &amp;amp;sin_size);
    if (new_fd == -1) {
      perror(&amp;#34;accept&amp;#34;);
      continue;
    }

    // Once we&amp;#39;ve accepted an incoming request, we can read from it into a buffer.
    int buffer;
    int bytes_received = recv(new_fd, &amp;amp;buffer, sizeof buffer, 0);
    if (bytes_received == -1) {
      perror(&amp;#34;recv&amp;#34;);
      continue;
    }

    // We need to unpack the received data.
    int number = unpack(buffer);
    printf(&amp;#34;Received a request: is %d prime?\n&amp;#34;, number);

    // Now, we can finally call the is_prime library!
    bool number_is_prime = is_prime(number);
    printf(&amp;#34;Sending response: %s\n&amp;#34;, number_is_prime ? &amp;#34;true&amp;#34; : &amp;#34;false&amp;#34;);

    // Note that we don&amp;#39;t have to pack a single byte.

    // We can now send it back.
    if (send(new_fd, &amp;amp;number_is_prime, sizeof number_is_prime, 0) == -1) {
      perror(&amp;#34;send&amp;#34;);
    }
    close(new_fd);
  }

}
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Finally, we can update our main function that runs on the client to use the new RPC library call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// client/basic_math_program_distributed.c
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdbool.h&amp;gt;

#include &amp;#34;is_prime_rpc_client.h&amp;#34;

int main(void) {
  // Prompt the user to enter a number.
  printf(&amp;#34;Please enter a number: &amp;#34;);
  // Read the user&amp;#39;s number. Assume they&amp;#39;re entering a valid number.
  int input_number;
  scanf(&amp;#34;%d&amp;#34;, &amp;amp;input_number);

  // Check if it&amp;#39;s prime, but now via the RPC library
  if (is_prime_rpc(input_number)) {
    printf(&amp;#34;%d is prime\n&amp;#34;, input_number);
  } else {
    printf(&amp;#34;%d is not prime\n&amp;#34;, input_number);
  }

  return 0;
}

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The RPC in action:&lt;/p&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;basic-math-distributed-animated.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;basic-math-distributed-animated.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;br&gt;
&lt;p&gt;If we run the server, we can run the client to distribute our prime check! Now, when the program is calling &lt;code&gt;is_prime_rpc&lt;/code&gt;, all network business happens in the background. We&amp;rsquo;ve successfully distributed the computation, and the client really is calling a remote procedure.&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&#34;so-you-can-appreciate-the-libraries&#34;&gt;&amp;hellip;so you can appreciate the libraries&lt;/h3&gt;
&lt;p&gt;This is a toy example, and although it shows some of the ideas, it&amp;rsquo;s really only a toy. Real frameworks (such as gRPC&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;) are understandably much more complex. Our implementation could use (at least) the following improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Discoverability&lt;/strong&gt;: in the toy example above, we assumed the server was running on &lt;code&gt;localhost&lt;/code&gt;. How would the RPC library know where to send the RPC? We would need some way to discover where the servers that can handle this RPC call live.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RPC type&lt;/strong&gt;: we&amp;rsquo;re dealing with a very simple server for a single RPC call. What if we wanted our server to serve two different RPCs, e.g. &lt;code&gt;is_prime&lt;/code&gt; and &lt;code&gt;get_factors&lt;/code&gt;? We would need a way to differentiate between the two in the requests that we&amp;rsquo;re sending to the server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packing&lt;/strong&gt;: Packing an integer is easy, and packing a single byte is even easier. What if we had a complicated data structure we wanted to send across the wire? What if we wanted to compress the data to save some bandwidth?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generating code automatically&lt;/strong&gt;: we don&amp;rsquo;t want to hand write all of the packing and networking code every time we write a new RPC. It would be great if we could just define our interface once and let the computer do the work for us, giving us just the stub to work with. This is where something like protocol buffers&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; come into play.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple languages&lt;/strong&gt;: along the previous lines, if we&amp;rsquo;re automatically generating the stubs, we might as well let it generate it in multiple languages, so cross-service and cross-language communication is still as easy as calling a function.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error &amp;amp; timeout handling&lt;/strong&gt;: what happens if the RPC fails? What if the network goes down, the server stops, the wifi drops&amp;hellip; What if we want to have a timeout to ensure we&amp;rsquo;re not waiting all day?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versioning&lt;/strong&gt;: let&amp;rsquo;s say you have all of the above, but you want to make a change to an RPC call that already has code generated and is running on multiple computers. How do you do it safely?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;All of the other caveats that go along with running servers&lt;/strong&gt;: threading, blocking, multiplexing, security, encryption, authorization&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Computer science is the business of standing on the shoulders of those that have come before us. It&amp;rsquo;s things like this that make you appreciate the libraries that do a lot of work for us already.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;http://web.eecs.umich.edu/~mosharaf/Readings/RPC.pdf&#34;&gt;&lt;em&gt;Implementing Remote Procedure Calls&lt;/em&gt;, Andrew D. Birrell and Bruce Jay Nelson&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://beej.us/guide/bgnet/&#34;&gt;Beej&amp;rsquo;s Guide to Network Programming&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://grpc.io/&#34;&gt;gRPC: &amp;ldquo;a modern open source high performance Remote Procedure Call (RPC) framework&amp;rdquo;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://developers.google.com/protocol-buffers&#34;&gt;Protocol Buffers&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>You&#39;re Reading This in the Future</title>
      <link>https://alexanderell.is/posts/youre-reading-this-in-the-future/</link>
      <pubDate>Fri, 16 Apr 2021 19:39:25 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/youre-reading-this-in-the-future/</guid>
      <description>&lt;p&gt;You&amp;rsquo;re reading this in the future. Isn&amp;rsquo;t that weird?&lt;/p&gt;
&lt;p&gt;Even I&amp;rsquo;m reading this in the future. Let&amp;rsquo;s say I write the last &amp;ldquo;C&amp;rdquo; in this sentence at 7:41:50 EDT in the evening of Friday April 16, 2021, or exactly 1618616510 seconds since January 1, 1970, UTC. Even as I started typing this next sentence, let alone editing this and pushing this to my blog, that time has passed, and we&amp;rsquo;ve all moved on.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s one of the most wonderful things about writing. Not only are we able to conjure up images in each others&amp;rsquo; brains across vast distances (quick, imagine two flamingos doing a crossword puzzle), but we&amp;rsquo;re able to do it across time as well. It&amp;rsquo;s like a mental bridge to exactly what the other person bothered to write down, hopefully edit, and share.  It&amp;rsquo;s often even a circular bridge to  your own mind, showing up in the form of shopping lists and reminders (note to self: don&amp;rsquo;t forget to buy dog food on your way home).&lt;/p&gt;
&lt;p&gt;Video, music, and pictures do this well too. I&amp;rsquo;ve recently gotten into watching reruns of &lt;em&gt;What&amp;rsquo;s My Line&lt;/em&gt;, a TV game show that started in the 1950s. The only problem with it is that every episode is available on YouTube&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, and with 16 years of weekly half-hour shows, I can easily spend a large chunk of my free time watching these old snapshots of the 50s and 60s.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s writing, though, that goes back even farther. One of the early panelists on &lt;em&gt;What&amp;rsquo;s My Line&lt;/em&gt; was Fred Allen, who had a successful career in vaudeville as a poor juggler and a fine comedian before transitioning to another successful career in radio. Many of Fred&amp;rsquo;s television and radio appearances were recorded, and those recordings live on for us to listen to today. His work in vaudeville, on the other hand, is much more ephemeral. Vaudeville itself is ephemeral, existing largely before things were recorded, dying in the early 1900s, and living on through acts that were able to transition to radio or movies.&lt;/p&gt;
&lt;p&gt;I recently read Fred Allen&amp;rsquo;s autobiography about growing up in Boston and entering vaudeville&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. Between the vaudeville theaters, the circuits, and the countless talents, it&amp;rsquo;s a snapshot into an era that is largely otherwise inaccessible. I&amp;rsquo;m glad he wrote about his early life, because it helps that time to live on in a way that other mediums don&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;(Scollay Square&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, home to many of the theatres where Fred performed in Boston, was later demolished in the late 1950s)&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also a human connection, where you can see yourself and the modern world in &lt;a href=&#34;https://books.google.pt/books?id=tSs2yV_F4n0C&amp;amp;printsec=frontcover&amp;amp;dq=Companion+Animals+%26+US&amp;amp;hl=en&amp;amp;sa=X&amp;amp;ei=r4OYU8jDJ6_50gWrwoDICg&amp;amp;redir_esc=y#v=onepage&amp;amp;q=in%20tears&amp;amp;f=false&#34;&gt;epitaphs for beloved pets in Ancient Greece and Rome&lt;/a&gt; or &lt;a href=&#34;https://en.wikipedia.org/wiki/Complaint_tablet_to_Ea-nasir&#34;&gt;a cuneiform complaint about customer service&lt;/a&gt;. Nearly everything has changed, but these motifs through time help us see what hasn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;At the same time, one of the joys is having a snapshot of an earlier time and viewing it with a modern lens. You&amp;rsquo;re reading this in the future, much like we&amp;rsquo;re both in the future reading about Ken Olsen saying in 1974 that he couldn&amp;rsquo;t see any use for a computer in someone&amp;rsquo;s home&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. Some day, maybe even already in your future, we&amp;rsquo;ll be looking back on this snapshot with some mix of hindsight and incredulity, and it&amp;rsquo;s fun to think about what future readers might question:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;You could just get in your car and drive as fast as it could go on the highway? No wonder so many people died in car accidents before self driving cars.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Why did the number of states in the US plateau at 50 for so long before going up again?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Your computers only dealt with 1s and 0s? No wonder they weren&amp;rsquo;t powerful enough.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;I hope your future is going well.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/channel/UChPE75Fvvl1HmdAsO7Nzb8w&#34;&gt;Every &lt;em&gt;What&amp;rsquo;s My Line&lt;/em&gt; episode on YouTube&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://www.goodreads.com/book/show/420790.Much_Ado_About_Me&#34;&gt;Fred Allen&amp;rsquo;s &lt;em&gt;Much Ado About Me&lt;/em&gt;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Scollay_Square&#34;&gt;Scollay Square&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://archive.org/details/creativecomputing-1980-04/page/n91/mode/2up&#34;&gt;Interview with Gordon Bell, Creative Computing Magazine (April 1980)&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Checking if the Pi is done</title>
      <link>https://alexanderell.is/posts/pi-cluster-monitoring/</link>
      <pubDate>Tue, 10 Nov 2020 20:48:00 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/pi-cluster-monitoring/</guid>
      <description>&lt;p&gt;I recently &lt;a href=&#34;https://alexanderell.is/posts/pi-cluster/&#34;&gt;put together a Raspberry Pi cluster&lt;/a&gt; to try out some toy distributed systems projects, and I wanted to start with a basic monitoring program to get my feet wet and work out the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Getting each node running and set up with software&lt;/li&gt;
&lt;li&gt;Running Go on each machine&lt;/li&gt;
&lt;li&gt;Deploying code to each machine&lt;/li&gt;
&lt;li&gt;Nodes listening &amp;amp; responding to RPCs&lt;/li&gt;
&lt;li&gt;Sending/receiving RPCs between laptop and nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;As I&amp;rsquo;ve previously mentioned, this isn&amp;rsquo;t meant to be production grade, since I would just grab one off the shelf if that was the goal. Instead, I want to work through the problem myself to get a feel for design decisions and making it work.&lt;/p&gt;
&lt;h2 id=&#34;system-design&#34;&gt;System design&lt;/h2&gt;
&lt;p&gt;We can set up a basic monitoring system by running a server on each of the nodes that listens for a heartbeat RPC. As a first step, we can have the servers respond with an OK status, and anything other than an OK response can signal a problem. We can have our main node — the one that will be monitoring the others — make requests to the child nodes at a certain interval and report the results.&lt;/p&gt;
&lt;p&gt;A quick sketch would look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;system-diagram.png&#34; alt=&#34;Cluster Architecture diagram; 4 child nodes connected to a main node&#34;&gt;&lt;/p&gt;
&lt;p&gt;This means that there will be two main components to our code: a server running on each child node and a client running on the main node.&lt;/p&gt;
&lt;h3 id=&#34;child-node-code&#34;&gt;Child node code&lt;/h3&gt;
&lt;p&gt;Each child node will run a small server that waits for a heartbeat request and responds immediately with &amp;ldquo;OK&amp;rdquo;. We can keep this very simple, first by defining the RPC arguments and response:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Args is the empty input to the heartbeat RPC.
type Args struct{}

// Reply is a simple boolean response.
type Reply struct {
  OK bool
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then create an RPC server that listens for a heartbeat RPC on a given port. We&amp;rsquo;ll want our server to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Listen for incoming RPCs on a certain port&lt;/li&gt;
&lt;li&gt;When we receive a heartbeat RPC, respond with OK&lt;/li&gt;
&lt;li&gt;Print some debug information&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;This comes together quickly with Go&amp;rsquo;s RPC package:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
This is a simple heartbeat server we can run on the cluster nodes. It just waits
for any heartbeat and responds.
*/

package main

import (
  &amp;#34;log&amp;#34;
  &amp;#34;net&amp;#34;
  &amp;#34;net/http&amp;#34;
  &amp;#34;net/rpc&amp;#34;
  heartbeat &amp;#34;pi-cluster-monitoring/rpc&amp;#34;
)

// Server is a basic heartbeat server.
type Server int

// Heartbeat is a simple check to see if the server is still alive and responding.
func (t *Server) Heartbeat(args *heartbeat.Args, reply *heartbeat.Reply) error {
  reply.OK = true
  log.Println(&amp;#34;Received Heartbeat. Sending OK.&amp;#34;)
  return nil
}

func main() {
  heartbeatServer := new(Server)
  rpc.Register(heartbeatServer)
  rpc.HandleHTTP()
  l, err := net.Listen(&amp;#34;tcp&amp;#34;, &amp;#34;:1234&amp;#34;)
  if err != nil {
    log.Fatal(&amp;#34;listen error:&amp;#34;, err)
  }

  log.Println(&amp;#34;Listening on port 1234&amp;#34;)
  go http.Serve(l, nil)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then run this on each of the child nodes, and they are now ready to be contacted.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;waiting-nodes.png&#34; alt=&#34;Nodes waiting for RPCs&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&#34;main-node-code&#34;&gt;Main node code&lt;/h3&gt;
&lt;p&gt;Next, we can build a small client that will periodically check on our listening child nodes by doing the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make an RPC to each of the child nodes and report the status, either OK or an error if something went wrong&lt;/li&gt;
&lt;li&gt;Wait a little bit&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
This is a simple heartbeat client we can run on a monitoring computer (i.e. not
the nodes). It wil periodically query the nodes and report their status.
*/

package main

import (
  &amp;#34;fmt&amp;#34;
  &amp;#34;net/rpc&amp;#34;
  &amp;#34;os&amp;#34;
  heartbeat &amp;#34;pi-cluster-monitoring/rpc&amp;#34;
  &amp;#34;time&amp;#34;
)

// HeartbeatIntervalInSeconds determines how often the heartbeats are sent.
var HeartbeatIntervalInSeconds = 5

func sendHeartbeat(serverAddress string) {
  client, err := rpc.DialHTTP(&amp;#34;tcp&amp;#34;, serverAddress+&amp;#34;:1234&amp;#34;)
  if err != nil {
    fmt.Println(&amp;#34;Server &amp;#34;+serverAddress+&amp;#34; is not OK. dialing: &amp;#34;, err)
    return
  }

  args := &amp;amp;heartbeat.Args{}
  var reply heartbeat.Reply
  err = client.Call(&amp;#34;Server.Heartbeat&amp;#34;, args, &amp;amp;reply)
  if err != nil {
    fmt.Println(&amp;#34;Server &amp;#34;+serverAddress+&amp;#34; is not OK. error calling Heartbeat: &amp;#34;, err)
    return
  }

  if !reply.OK {
    fmt.Println(&amp;#34;Server &amp;#34;+serverAddress+&amp;#34; is not OK.&amp;#34;)
    return
  }
  fmt.Println(&amp;#34;Server &amp;#34;+serverAddress+&amp;#34; is OK.&amp;#34;)
}

func main() {
  allServerIPs := []string{
    os.Getenv(&amp;#34;PI1&amp;#34;),
    os.Getenv(&amp;#34;PI2&amp;#34;),
    os.Getenv(&amp;#34;PI3&amp;#34;),
    os.Getenv(&amp;#34;PI4&amp;#34;)}

  for true {
    fmt.Println(&amp;#34;\nSending heartbeats&amp;#34;)
    for _, server := range allServerIPs {
      go sendHeartbeat(server)
    }

    time.Sleep(time.Duration(HeartbeatIntervalInSeconds) * time.Second)
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;ve passed in the addresses of the child nodes as environment variables, so this assumes an unchanging set of child nodes we know about in advance&lt;/li&gt;
&lt;li&gt;We can easily send the requests (roughly) in parallel to the child nodes via goroutines (though for N=4 this is just for fun)&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;Once this is started on the main node (my laptop) with the child nodes already running, it immediately begins querying the child nodes and reporting their statuses:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;monitoring.png&#34; alt=&#34;Monitoring program in progress&#34;&gt;&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&#34;potential-improvements&#34;&gt;Potential improvements&lt;/h3&gt;
&lt;p&gt;This is a very simple approach, but it was helpful for the goals I mentioned above. The basic system is set up, the nodes are communicating, and the basic monitoring will be helpful to use for future projects. Personally, I find it better to implement the minimal version that achieves the goals and concepts, as I could easily spend hours tinkering with it. Kind of like when you toil away tweaking the CSS on your personal blog instead of really writing: it&amp;rsquo;s important to actually do what you set out to do.&lt;/p&gt;
&lt;p&gt;That being said, there are a few things I&amp;rsquo;d love to do to improve this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Include some CPU and memory information in the heartbeat to see how busy the Pis are&lt;/li&gt;
&lt;li&gt;Run the server on the nodes on startup so they&amp;rsquo;re always available in the background&lt;/li&gt;
&lt;li&gt;Add some nicer reporting in the client.go output and history logging&lt;/li&gt;
&lt;li&gt;Improve the deployment strategy, which is currently ssh&amp;rsquo;ing into each one then git pulling&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h3 id=&#34;more-complexity&#34;&gt;More complexity&lt;/h3&gt;
&lt;p&gt;If this was supposed to be a robust monitoring system for a busy cluster, what would we have to add? A few (non-exhaustive) thoughts on possible problems and tradeoffs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The list of nodes is static. How would we make a monitoring system that allowed for dynamically adding/removing nodes?
&lt;ul&gt;
&lt;li&gt;We could keep the current setup but also have the main node run an RPC server with a &amp;ldquo;Register&amp;rdquo; RPC, or something similar, that would allow new nodes to add themselves to the main node&amp;rsquo;s list of children. We could also have a &amp;ldquo;Deregister&amp;rdquo; RPC for removing nodes from this list that could be callable from a CLI, for instance, when we knew a node had been removed from the system.&lt;/li&gt;
&lt;li&gt;Right now we have the client initiating the RPC to the nodes, which requires it to know about each node before checking in. If we switched this and had the child nodes make requests to the main node, we could just have each child know the location of the main node.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The monitoring client is on the same network as the nodes themselves, so if I was surfacing this information in a webpage served from my laptop and my home network went down, the monitoring system would go down too.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s only one monitoring client, and if my laptop dies, I wouldn&amp;rsquo;t be able to see if they&amp;rsquo;re up. Ideally there would be another avenue for checking.&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;There are plenty of improvements to make, but this is a good start.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Building a Raspberry Pi Cluster for Building Toy Distributed Systems</title>
      <link>https://alexanderell.is/posts/pi-cluster/</link>
      <pubDate>Sat, 24 Oct 2020 20:48:06 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/pi-cluster/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve recently been getting more interested in distributed systems, and I wanted to get experience building some of the concepts I&amp;rsquo;ve read about.&lt;/p&gt;
&lt;p&gt;My interest lies more on the software and system design side &amp;ndash; more &amp;ldquo;how would we design and build an example of a distributed file system across four computers?&amp;rdquo; than &amp;ldquo;how do I make a reliable home server to play media?&amp;rdquo; &amp;ndash; which is why my focus is on playing around with some toy software problems instead of worrying about what a real production hardware setup would look like. As with anything, it&amp;rsquo;s almost too easy to get caught up in the minor details instead of what you meant to work on, and I feel like I could spend days agonizing over hardware choices.&lt;/p&gt;
&lt;p&gt;That being said, I did want to work with real hardware, which makes it more interesting than running VMs. There&amp;rsquo;s something about physically disconnecting ethernet switches to see the effect on the software that&amp;rsquo;s intriguing, and doubly so if the software can handle it gracefully.&lt;/p&gt;
&lt;p&gt;I want to get a little more experience with the concepts behind some distributed systems papers and projects I&amp;rsquo;ve learned about. I&amp;rsquo;ve heard that the true devil of distributed systems lies in the details at scale, which isn&amp;rsquo;t really possible for my tiny cluster, but I&amp;rsquo;m planning to work through these projects to get a better feel for some of the general ideas. I&amp;rsquo;ve found that programming an implementation is one of the best ways to understand a concept, even if it&amp;rsquo;s a toy implementation, and I&amp;rsquo;m looking forward to doing so for some of the papers I&amp;rsquo;ve been reading.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m planning to write most of the software in Go because I&amp;rsquo;ve enjoyed working with its RPC library in the past and I want to get more experience working with the language. I do mostly C++/Java/JavaScript at work, and it&amp;rsquo;s always nice to explore outside of the comfort zone.&lt;/p&gt;
&lt;p&gt;Anyways, back to the cluster; I ended up picking N=4 for my little cluster and drew up a quick diagram of what my plan:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;cluster-architecture.png&#34; alt=&#34;Cluster Architecture diagram; 4 Raspberry Pis connected to a network switch&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;The planned cluster, with 4 Raspberry Pis connected to power on the left and a network switch on the right&lt;/em&gt;
&lt;/center&gt;
&lt;br&gt;
&lt;p&gt;This ended up actually being pretty close to reality, which is always nice for design diagrams. I&amp;rsquo;ll spare you the order details (I&amp;rsquo;ve found these can quickly get out of date), but after the components trickled in, I wired up the Pis from 1-4 from bottom to top with a helpful case to keep them separate.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;cluster-picture.jpeg&#34; alt=&#34;Photo of the 4 Raspberry Pis in action&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;The cluster in action&lt;/em&gt;
&lt;/center&gt;
&lt;br&gt;
&lt;p&gt;So far I&amp;rsquo;ve done the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installed the Raspberry Pi OS Lite on each MicroSD &amp;amp; enabled SSH&lt;/li&gt;
&lt;li&gt;Grabbed the IPs and SSH&amp;rsquo;ed into each&lt;/li&gt;
&lt;li&gt;Had to re-image a MicroSD after I didn&amp;rsquo;t save the password I generated&lt;/li&gt;
&lt;li&gt;Did some basic ssh/scp/config setup to install .vimrc/tmux/vim/git/Go&lt;/li&gt;
&lt;li&gt;Wrote up a basic heartbeat monitoring program to get them talking to each other&lt;/li&gt;
&lt;li&gt;Spent 15 minutes debugging before I realized the switch was turned off&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;p&gt;My next steps are probably something like this, time and motivation permitting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flesh out and write up the heartbeat monitoring program&lt;/li&gt;
&lt;li&gt;Design and implement a basic distributed file system that I can reuse for future projects
&lt;ul&gt;
&lt;li&gt;Probably will follow along with GFS but with some appropriate adjustments&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Work through a few papers and adapt them to the mini-cluster
&lt;ul&gt;
&lt;li&gt;Tentatively thinking MapReduce, some KV store (implementing consistent hashing), Raft, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;br&gt;
&lt;p&gt;Stay tuned for more :)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Debugging a launch-blocking issue</title>
      <link>https://alexanderell.is/posts/debugging/</link>
      <pubDate>Sun, 23 Aug 2020 23:14:15 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/debugging/</guid>
      <description>&lt;p&gt;It was around 4PM a few Fridays ago when I heard that something had gone wrong, and I volunteered (was volunteered?) to help fix it. There was a broken internal configuration that blocked future immediate work for an upcoming launch that same day. I hadn&amp;rsquo;t worked on this particular system, just similar things before, but because I&amp;rsquo;m decent at visualizing and debugging these things, my colleague asked me for help with it.&lt;/p&gt;
&lt;p&gt;In retrospect, it was something that probably said &amp;ldquo;this should never happen&amp;rdquo; in the documentation. I scoped it out with my colleague who was more familiar with it, since he had been working on it right before things went south and was convinced he had caused the bug in one of his setup steps. He was more familiar with a similar but slightly different configuration where this issue wasn&amp;rsquo;t even possible, and he wasn&amp;rsquo;t aware that an extra fail-safe was needed to avoid what he thought had gone wrong.&lt;/p&gt;
&lt;p&gt;I looked at the system, confirmed the problem matched what he thought it was, and started thinking about how to fix it. Initially I tried using a basic tool, but it didn&amp;rsquo;t work (issue of scale). After a quick search online, I found a few potential solutions for the same issue, but they all seemed either too complicated or involved some tooling we didn&amp;rsquo;t have readily available.&lt;/p&gt;
&lt;p&gt;After a little more digging and thinking, it turned out we had some existing tool that we could reuse to fix the incorrect configuration, even though it was designed for a different use (who knows why it was still lying around).  After some paired work on the problem, we were able to go inside the system and correct the configuration, which unblocked my colleague and the subsequent launch.&lt;/p&gt;
&lt;p&gt;To help encourage our blameless culture, after some very light teasing I brought up the fact that anyone could have easily made this mistake &amp;ndash; myself very much included &amp;ndash; and helped ensure that the extra fail-safe was in place for whoever used it next.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I&amp;rsquo;ve been intentionally vague with the story because it wasn&amp;rsquo;t a software bug at work; it was a lake-side sailboat problem. My cousin had accidentally pulled one end of his sailboat&amp;rsquo;s halyard up through the mast while rigging the boat, and the end he needed threaded through at the bottom was instead at the top of the 20 foot mast.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;skied-halyard.png&#34; alt=&#34;Visualization of skied halyard&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;Visualization of what went wrong (but not to scale)&lt;/em&gt;
&lt;/center&gt;
&lt;br&gt;
&lt;p&gt;This boat, a 420, had a mast where the halyard went through the middle of the mast, while other 420s he had sailed just had it going up the side. He needed to be able to pull it back through the mast to hoist the sail (blocking his late-afternoon sailboat launch). This was pretty easy to confirm, and after trying my first attempt with a short wire (an issue of scale&amp;hellip;) and finding that most of the solutions online were kind of complicated (&amp;ldquo;tie the end of the rope to a bolt and use a magnet to feed it through&amp;rdquo;), we found some sturdy long wire we reused to feed the end of the rope through the mast. Afterwards, for the failsafe, he tied a few figure 8 knots at the end to ensure it wouldn&amp;rsquo;t get pulled up again.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been thinking a lot recently about why fixing problems with physical things is so satisfying to me, and I was thinking through how familiar the debugging experience was between this real-life fix and and most of the software debugging I do. It&amp;rsquo;s really all the same!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Code Review and Criticism</title>
      <link>https://alexanderell.is/posts/me-and-my-work/</link>
      <pubDate>Wed, 03 Jun 2020 00:07:04 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/me-and-my-work/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been working in software for a few years, and I&amp;rsquo;ve come to take it for granted that I am not my work. My work exists in some separate sphere, where it can easily be critiqued, dissected, refactored, or improved by me or anyone else. It&amp;rsquo;s as if it&amp;rsquo;s in a separate pond from my private one, where any ripples don&amp;rsquo;t cross the pebbly barrier to the rest of me. I&amp;rsquo;m still proud of the things I make and still take ownership over code, projects, and problems, but those things are separate from me.&lt;/p&gt;
&lt;p&gt;This makes working with software much easier. Any comment or constructive criticism is clearly not about me; it&amp;rsquo;s about this thing I created. If it can be improved, that means there&amp;rsquo;s something to learn. If there&amp;rsquo;s something wrong with it, it means that there&amp;rsquo;s something to remember the next time around. It removes any kind of validation or personal valuation from code reviews and ensures it&amp;rsquo;s a collective effort towards solving problems and improving the codebase.&lt;/p&gt;
&lt;p&gt;This may sound self-evident, but it wasn&amp;rsquo;t always like this for me. I remember my first proper code review pretty clearly. After learning to code mostly through solo side projects without any external input, let alone code reviews, and working a basic role doing basic dev work, also without code reviews, I ended up at a proper startup with more senior developers and a modern development process.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;together.png&#34; alt=&#34;My work, being a part of me&#34;&gt;&lt;/p&gt;
&lt;p&gt;I remember sending out my first nontrivial code for review, then getting it back with what felt like a ton of comments. It&amp;rsquo;s funny to think back to, because now it would easily be no sweat to get a comment-heavy review, especially if it was a new codebase or a new language for me. At the time, it was a lot of sweat. I remember feeling embarrassed that there was so much wrong with it. Had I been learning wrong this whole time? Was I really qualified to be paid for this? My manager said it was a good start and was very supportive, but the imposter syndrome in the back of my head told me it didn&amp;rsquo;t matter. It felt like I had learned a word only by seeing it in writing, then I mispronounced it when trying to show that I understood the concept. This was one of the first times that I was opening up code I had written for feedback, and not being experienced with criticism of my code, it felt like it was a reflection onto me personally.&lt;/p&gt;
&lt;p&gt;Of course, it ended up being OK. The comments were all helpful, and there was nothing personal about it. I got through that first review, and as one code review faded into ten, then dozens, then hundreds, this muscle built up over time, growing the mental separation between myself and my output.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;separated.png&#34; alt=&#34;My work, separate from me&#34;&gt;&lt;/p&gt;
&lt;p&gt;I think putting creative work that feels like a part of yourself out there is scary at first. I&amp;rsquo;ve experienced it with having others review my code, cooking for other people, sharing a drawing I made with someone, or even writing publicly like this for anyone to read. At first it feels like it&amp;rsquo;s a part of you, and any negativity about this thing you&amp;rsquo;ve created is a direct reflection onto you. Like anything, I think practice building that barrier is important. Of course, this requires good faith on behalf of the reviewer and assumes that any feedback is constructive, if not at least correct.&lt;/p&gt;
&lt;p&gt;This odd skill of separating your work from yourself and getting comfortable with criticism isn&amp;rsquo;t something I ever saw mentioned when I was learning to code, and I didn&amp;rsquo;t consciously think about it once I had built it up. It grew over time, like a callous on your hand you didn&amp;rsquo;t notice until you happened to think about your first blister in the same spot.&lt;/p&gt;
&lt;p&gt;This came to mind recently as I was walking through the importance of healthy code reviews with the summer interns on our team at work, where working to foster a supportive and constructive environment reminded me of my first experiences with this odd balance. It makes me think about the other skills you build over time subconsciously, and it makes me wonder which ones I&amp;rsquo;ve forgotten I had to learn. I guess I&amp;rsquo;ll have to do my best to remember them.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Don&#39;t touch my clipboard</title>
      <link>https://alexanderell.is/posts/taking-over-my-clipboard/</link>
      <pubDate>Mon, 17 Feb 2020 18:01:11 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/taking-over-my-clipboard/</guid>
      <description>&lt;h2 id=&#34;looking-for-an-em-dash&#34;&gt;Looking for an em dash&lt;/h2&gt;
&lt;p&gt;I was recently trying to find an &lt;a href=&#34;https://en.wikipedia.org/wiki/Dash#En_dash_versus_em_dash&#34;&gt;em dash&lt;/a&gt; character to use in a piece of writing. Since I don&amp;rsquo;t have the shortcut memorized—for future reference, shift+option+minus on Mac—I did a quick search for it. I didn&amp;rsquo;t see one on the search results page to copy, so I clicked into the first result: &amp;ldquo;The Punctuation Guide&amp;rdquo;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;the-punctuation-guide.png&#34; alt=&#34;Screenshot of The Punctuation Guide, the website I visited&#34;&gt;
&lt;em&gt;From &lt;a href=&#34;https://www.thepunctuationguide.com/em-dash.html&#34;&gt;https://www.thepunctuationguide.com/em-dash.html&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a sucker for a good copy and paste, so I selected the em dash character, copied it, and pasted back into my doc. One problem though:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit—

From https://www.thepunctuationguide.com/em-dash.html
© 2020 thepunctuationguide.com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Immediately suspicious. It looks like this site is overriding the copied text somehow, and though I select a single character, it&amp;rsquo;s augmenting my copied text with a citation and copyright information.&lt;/p&gt;
&lt;h2 id=&#34;javascript-and-client-side-code&#34;&gt;JavaScript and client-side code&lt;/h2&gt;
&lt;p&gt;Two of the good things about JavaScript are that it runs on your computer and it&amp;rsquo;s easily inspectable. I&amp;rsquo;ve worked with JavaScript quite a bit, but I&amp;rsquo;ve never modified a user&amp;rsquo;s copy command. Out of curiousity I inspected the page to see if I could figure how they were doing this.&lt;/p&gt;
&lt;p&gt;My first instinct was that they were using some generically named &amp;ldquo;copy-copyright&amp;rdquo; library somewhere, so I inspected the HTML. JavaScript tags usually come at the end of the page, so straight to the bottom.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;script-tag.png&#34; alt=&#34;Screenshot of the HTML of site with the JS script visible&#34;&gt;&lt;/p&gt;
&lt;p&gt;Opening this up shows the 15 responsible lines of JS:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// JavaScript Document
function addLink() {

    var selection = window.getSelection(),
        pagelink = &amp;#39;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt; From &amp;#39; + document.location.href + &amp;#39;&amp;lt;br /&amp;gt;Â© 2020 thepunctuationguide.com&amp;#39;,
        copytext = selection + pagelink,
        newdiv = document.createElement(&amp;#39;div&amp;#39;);


    newdiv.style.position = &amp;#39;absolute&amp;#39;;
    newdiv.style.left = &amp;#39;-99999px&amp;#39;;


    document.body.appendChild(newdiv);
    newdiv.innerHTML = copytext;
    selection.selectAllChildren(newdiv);

    window.setTimeout(function () {
        document.body.removeChild(newdiv);
    }, 100);
}

document.addEventListener(&amp;#39;copy&amp;#39;, addLink);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;From &lt;a href=&#34;https://www.thepunctuationguide.com/js/copyright.js&#34;&gt;https://www.thepunctuationguide.com/js/copyright.js&lt;/a&gt; at the time of writing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This creates a function &lt;em&gt;addLink&lt;/em&gt; that does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the object that represents the user&amp;rsquo;s current selection&lt;/li&gt;
&lt;li&gt;Build a string of HTML elements with the current page&amp;rsquo;s link and copyright info&lt;/li&gt;
&lt;li&gt;Concatenate the user&amp;rsquo;s selection and that HTML&lt;/li&gt;
&lt;li&gt;Create a new empty div element on the page&lt;/li&gt;
&lt;li&gt;Move it well off the screen to the left&lt;/li&gt;
&lt;li&gt;Add the new div element to the page with that the user&amp;rsquo;s selection and that HTML&lt;/li&gt;
&lt;li&gt;Update the current selection to be the children of that div (aka select the concatenated string of user selection and attribution)&lt;/li&gt;
&lt;li&gt;In 100ms, get rid of that element&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It then adds this function as a listener on the copy event, so every time the browser hears that you&amp;rsquo;re copying, this is run first. The magic is from &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection&#34;&gt;&lt;code&gt;window.getSelection()&lt;/code&gt;&lt;/a&gt;, which gives access to the string that the user has selected. It then updates this selection with &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Selection/selectAllChildren&#34;&gt;&lt;code&gt;selection.selectAllChildren&lt;/code&gt;&lt;/a&gt;, which updates the selected text to be the text in the newly created div.&lt;/p&gt;
&lt;p&gt;Now that I know how it&amp;rsquo;s done, another unusual thing makes sense—when I highlight text and hit copy, the highlight goes away. This is because the focus and selection has shifted to that other div.&lt;/p&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;highlight-disappears.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;highlight-disappears.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;p&gt;Interestingly, this function must run before the copy event is fully bubbled up. This means that it&amp;rsquo;s intercepting the copy event, modifying the text that&amp;rsquo;s selected, and passing it on, like a man-in-the-middle attack against your clipboard.&lt;/p&gt;
&lt;h2 id=&#34;this-is-bad&#34;&gt;This is bad&lt;/h2&gt;
&lt;p&gt;This falls into the category of not respecting the user&amp;rsquo;s actions. Attribution is important, but changing things outside of what the site should be changing is a bad pattern. It&amp;rsquo;s like smooth scrolling, where the page commandeers the user&amp;rsquo;s scroll actions. I&amp;rsquo;m strongly of the opinion that you shouldn&amp;rsquo;t do things behind the scenes that diverge from the user&amp;rsquo;s normal expectations.&lt;/p&gt;
&lt;p&gt;One other thing I&amp;rsquo;m also curious about is the validity of the copyright for small text selections. I don&amp;rsquo;t know much about copyrights, but I&amp;rsquo;d be curious if an atomic selection from a creative work (like a single character, &amp;ldquo;—&amp;rdquo;) would fall under the same copyright as the whole work.&lt;/p&gt;
&lt;p&gt;Ironically, a little reverse searching reveals that this code was copied verbatim without attribution from &lt;a href=&#34;https://stackoverflow.com/questions/2026335/how-to-add-extra-info-to-copied-web-text?rq=1&#34;&gt;this StackOverflow post&lt;/a&gt; (see &amp;ldquo;Manipulating the selection&amp;rdquo; from the top answer). Maybe copy/paste isn&amp;rsquo;t so bad?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Harnessing the Power of Shower Thoughts</title>
      <link>https://alexanderell.is/posts/trust-in-your-unconscious/</link>
      <pubDate>Sat, 16 Nov 2019 21:43:06 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/trust-in-your-unconscious/</guid>
      <description>&lt;p&gt;One of the best ways to learn and come up with new ideas is to focus intensely on a problem, then let your mind wander. I&amp;rsquo;ve enjoyed doing this when problem solving and learning, and recently I&amp;rsquo;ve been thinking about how weird it is to build trust in your unconscious to do the work for you, especially when it comes to technical work.&lt;/p&gt;
&lt;h3 id=&#34;letting-your-mind-focus-and-letting-your-mind-wander&#34;&gt;Letting your mind focus and letting your mind wander&lt;/h3&gt;
&lt;p&gt;The first recommendation I have for anyone taking a meta look at learning is Barbara Oakley&amp;rsquo;s &lt;a href=&#34;https://www.coursera.org/learn/learning-how-to-learn&#34;&gt;&lt;em&gt;Learning How To Learn&lt;/em&gt;&lt;/a&gt;. I&amp;rsquo;ve found it to be a tremendous resource for building better study habits and learning skills, and recommending the course in various threads about learning is &lt;a href=&#34;https://hn.algolia.com/?dateRange=all&amp;amp;page=0&amp;amp;prefix=true&amp;amp;query=author%3Aotras%20learning%20how%20to%20learn&amp;amp;sort=byPopularity&amp;amp;type=comment&#34;&gt;easily the majority of my comments on Hacker News&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of my biggest takeaways from the course was the balance between actively focusing and letting your mind wander, or the &lt;em&gt;focused&lt;/em&gt; and &lt;em&gt;diffuse&lt;/em&gt; modes of thinking. The focused mode is for filling your mind with information, and the diffuse mode is great at processing that information, forming connections between existing concepts, and developing new ideas.&lt;/p&gt;
&lt;p&gt;In the course, one of the examples of consciously switching between the two modes to develop new ideas is the nap strategy employed by Salvadore Dali, where after a long period of focused work on a project, he would retire for a brief nap while holding a key in one hand above a plate. As he drifted off, his mind would wander, forming new thoughts. As soon as he entered proper sleep, his grip would relax and the key would fall to the plate, making enough noise to wake him and call him back to his work, the new ideas fresh in his head. Thomas Edison would also reportedly do the same with a ball bearing in his hand instead, harnessing the amorphous diffuse mode to come up with new ideas.&lt;/p&gt;
&lt;p&gt;In a recent New Yorker article, &lt;a href=&#34;https://www.newyorker.com/culture/annals-of-inquiry/the-myth-and-magic-of-generating-new-ideas&#34;&gt;&lt;em&gt;The Myth and Magic of Generating New Ideas&lt;/em&gt;&lt;/a&gt;, Dan Rockmore goes into detail about working as a mathematician and coming up with new insights while jogging or lifting weights after focusing hard on the problem: &amp;ldquo;it’s just a feeling of being free, of forgetting for a moment that we are bound by gravity and logic and convention, of letting the magic happen.&amp;rdquo;&lt;/p&gt;
&lt;h3 id=&#34;letting-the-magic-happen-in-practice&#34;&gt;Letting the magic happen in practice&lt;/h3&gt;
&lt;p&gt;I started paying more attention to this balance around the time when I was learning to program, and I&amp;rsquo;ve been able to use these two modes when learning how to program, learning in an academic environment, and solving problems that come up in my daily work.&lt;/p&gt;
&lt;p&gt;Working in software has graciously given me the opportunity to often have no immediate idea how to solve the problem in front of me. There&amp;rsquo;s a certain kind of cruel balance to the work; if it was exactly the same problem we were solving before, we&amp;rsquo;d just copy and paste what we did there. It&amp;rsquo;s usually not the same problem, so it usually needs a new solution.&lt;/p&gt;
&lt;p&gt;Sometimes after investigating a problem, thinking through the edge cases, and trying an attempt or two, the right solution isn&amp;rsquo;t immediately there. Sometimes you look at a problem and something comes to you, while other times, you draw a blank (and occasionally this is where the nagging imposter syndrome thoughts sneak out of their grungy caves). My usual next steps involve writing things down, trying some code, searching for similar problems, rubber-ducking, or asking a coworker.&lt;/p&gt;
&lt;p&gt;If none of this works, I&amp;rsquo;ve found that one of the best ways to move forward with a problem is to get away from the problem. If it&amp;rsquo;s halfway through the day, going for a walk around the block (importantly, without just going on my phone) or going for a swim will often let me realize something I missed that proves to be the key when I get back to my desk. If it&amp;rsquo;s close to the end of the day, heading home and letting the problem sit overnight is often enough to unblock me for the next morning. I&amp;rsquo;ve lost track of how many times I&amp;rsquo;ve realized a key insight on the train home, only 15 minutes of unfocused day dreaming since I was just looking intently at the code.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve found that this has also been very helpful for learning new concepts. Focusing on something new in a lecture or problem set and giving it some time to soak while my mind is floating seems to let the ideas settle better than if I was just reviewing my notes.&lt;/p&gt;
&lt;h3 id=&#34;my-own-recipe-for-striking-the-balance&#34;&gt;My own recipe for striking the balance&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Take some time to focus entirely on the problem&lt;/li&gt;
&lt;li&gt;Take some time to &lt;em&gt;not&lt;/em&gt; focus on &lt;em&gt;any&lt;/em&gt; problem&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This sounds deceivingly simple, but like any advice you read in the &amp;ldquo;How To&amp;rdquo; books, the hard part isn&amp;rsquo;t understanding it&amp;rsquo;s a good idea. The hard part is actually applying it and seeing if it helps at all.&lt;/p&gt;
&lt;h3 id=&#34;ways-ive-been-able-to-let-my-mind-wander&#34;&gt;Ways I&amp;rsquo;ve been able to let my mind wander&lt;/h3&gt;
&lt;p&gt;There are a few ways in particular that I&amp;rsquo;ve found to be especially good at shifting my mind towards the diffuse mode.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Going for a walk &lt;strong&gt;without my phone&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Running&lt;/li&gt;
&lt;li&gt;Taking a nap&lt;/li&gt;
&lt;li&gt;Taking a shower&lt;/li&gt;
&lt;li&gt;Swimming&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;things-ive-found-dont-work&#34;&gt;Things I&amp;rsquo;ve found don&amp;rsquo;t work&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Going on my phone mindlessly&lt;/li&gt;
&lt;li&gt;Playing video games&lt;/li&gt;
&lt;li&gt;Checking my email&lt;/li&gt;
&lt;li&gt;Refreshing Hacker News, Twitter, Instagram or (insert favorite site)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s very easy to fool myself into thinking I&amp;rsquo;m harnessing this weird power when really I&amp;rsquo;m just noodling time away. These usually create some middle stage, with my brain distracted but not fully relaxed enough to sink into diffuse thinking. I&amp;rsquo;m also convinced that half of the reason shower thoughts are a thing is because it&amp;rsquo;s one of the only times that it&amp;rsquo;s especially hard to fill the time with a screen. Archimedes wasn&amp;rsquo;t on Instagram in the bath.&lt;/p&gt;
&lt;h3 id=&#34;learning-to-live-with-the-magic&#34;&gt;Learning to live with the magic&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s a weird balance here between the highly logical and analytical skills and work involved in programming and the complete unknown that is the unconscious and how new ideas are generated. There&amp;rsquo;s something unsatisfying about resorting to a black box of neurons and brain matter that tosses different ideas around in the background without you, like a clothes dryer filled of ideas that&amp;rsquo;s tumbling while you sleep. When it&amp;rsquo;s done, you might be missing a sock, but somehow these nebulous thoughts came together into a solution.&lt;/p&gt;
&lt;p&gt;This uncertainty is made worse by the fact that you can&amp;rsquo;t tell &lt;em&gt;how&lt;/em&gt; your mind surfaced the idea. How did you come up with this solution? How do you know you&amp;rsquo;ll come up with a solution next time? The idea that you can trust yourself to come up with another idea in the future is strange.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m learning to live with the idea that I can&amp;rsquo;t force it, but I can guide it. I&amp;rsquo;m more comfortable with the idea now, and more than anything, I&amp;rsquo;m just glad it works.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Dark pattern for autoplaying videos: clickjacking pause</title>
      <link>https://alexanderell.is/posts/intercept-pause/</link>
      <pubDate>Thu, 24 Oct 2019 20:40:52 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/intercept-pause/</guid>
      <description>&lt;p&gt;I recently saw a common &lt;a href=&#34;https://en.wikipedia.org/wiki/Clickjacking&#34;&gt;clickjacking&lt;/a&gt; dark pattern when trying to pause a video the other day.&lt;/p&gt;
&lt;p&gt;On a Bloomberg news autoplaying video, the sound is initially muted.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;autoplay-video.png&#34; alt=&#34;Screenshot of autoplaying Bloomberg video&#34;&gt;&lt;/p&gt;
&lt;br&gt;
Clicking anywhere on the video will unmute it. It would be pretty easy to have an invisible element overlaying the video that listens for a click, and it looks like that&#39;s probably what we have here:
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;overlay.png&#34; alt=&#34;Screenshot of devtools focusing on video overlay element&#34;&gt;&lt;/p&gt;
&lt;br&gt;
What&#39;s interesting is that the control panel (pause, play, scrubbing, etc.) at the bottom of the video is initially shown for a few seconds. This makes it look like you can click it.
&lt;p&gt;If you try to click pause, your click will instead be intercepted by that covering layer and unmute the video. Only then is the control panel clickable.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;hidden-pause.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;hidden-pause.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;br&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;User action&lt;/strong&gt;: mouse click on pause button&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expected result&lt;/strong&gt;: pause video&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actual result&lt;/strong&gt;: unmute video&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Classic misdirection.  News websites are notorious for bad UX, and Bloomberg lives up to hype.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Clickbait Snail Mail</title>
      <link>https://alexanderell.is/posts/clickbait-snail-mail/</link>
      <pubDate>Sat, 19 Oct 2019 20:47:19 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/clickbait-snail-mail/</guid>
      <description>&lt;p&gt;Junk mail is so interesting at its odd intersection between design, advertising, and psychology.&lt;/p&gt;
&lt;p&gt;I usually try to pay attention to the ways that things around me are designed, and recently I&amp;rsquo;ve been saving some junk letters that are particularly interesting in their attention-grabbing efforts. Here are a few of the notable envelopes I&amp;rsquo;ve received in the past few months.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-if-we-didnt-put-any-information-on-it-and-made-it-look-exotic&#34;&gt;What if we didn&amp;rsquo;t put any information on it and made it look exotic?&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a letter I received with a colorful design and no information:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;air-mail-1.png&#34; alt=&#34;Air mail&#34;&gt;
&lt;img src=&#34;air-mail-2.JPG&#34; alt=&#34;Air mail two&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can tell by the pixelation that it&amp;rsquo;s all just printed on, and it&amp;rsquo;s actually a request for donations for a company that provides loans (but not donations?) to farmers in Africa.&lt;/p&gt;
&lt;p&gt;There are a few things that this design does.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;No return address&lt;/em&gt;: I don&amp;rsquo;t immediately know it&amp;rsquo;s junk from a non-profit, and it could be from someone I know. This could make me less likely to toss it immediately.
&lt;br&gt;
&lt;em&gt;Air mail stamps and old school border design&lt;/em&gt;: makes it look more interesting than your usual mail, which adds to the allure. Best case scenario it was really routed through Brussels.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-can-we-do-with-color&#34;&gt;What can we do with color?&lt;/h2&gt;
&lt;p&gt;Next up is a bright blue letter that stood out from its all-white neighbors.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;blueeeee.png&#34; alt=&#34;Blue&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bright blue color and embossed &amp;ldquo;hello&amp;rdquo;&lt;/em&gt;: these catch your eye with color and texture.
&lt;br&gt;
&lt;em&gt;Pre-qualified&lt;/em&gt;: classic.
&lt;br&gt;
&lt;em&gt;Lower case &amp;ldquo;hello&amp;rdquo;&lt;/em&gt;: signaling that it&amp;rsquo;s casual, not just corporate.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;mailing-to-people-who-registered-dogs-in-cambridge&#34;&gt;Mailing to people who registered dogs in Cambridge&lt;/h2&gt;
&lt;p&gt;I got a political mailer that stood out to me. It&amp;rsquo;s less about opening a letter, but it&amp;rsquo;s still advertising.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;dog-mailer.JPG&#34; alt=&#34;Dog Mailer&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Dog-focused&lt;/em&gt;: they clearly know I have a dog, so they sent me the dog mailer.
&lt;br&gt;
&lt;em&gt;Photo with a dog&lt;/em&gt;: Craig is clearly dog-friendly.
&lt;br&gt;
&lt;em&gt;Endorsed by Mass Voters for Animals and Sierra Club&lt;/em&gt;: lots of positive signaling.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;save-a-child-and-get-a-map&#34;&gt;Save a child &lt;strong&gt;and&lt;/strong&gt; get a map&lt;/h2&gt;
&lt;p&gt;This letter asks for your help and shows you a child that you could help. They also advertise a free gift, which is oddly a map of the United States.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;save-child.png&#34; alt=&#34;Save Child&#34;&gt;
&lt;img src=&#34;save-child-and-get-map.JPG&#34; alt=&#34;Save Child Get Map&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Personalized&lt;/em&gt;: this is no &amp;ldquo;To Whom It May Concern&amp;rdquo;; it&amp;rsquo;s &lt;em&gt;you&lt;/em&gt; that can help, and they call that out.
&lt;br&gt;
&lt;em&gt;Showing you a child you can help&lt;/em&gt;: this helps make the issue real, since you can directly see who you could help.
&lt;br&gt;
&lt;em&gt;Free map&lt;/em&gt;: this one is pretty odd to include, but when you get a gift by just opening the letter, opening the letter becomes even more appealing.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;action-required-for-your-electricity-account&#34;&gt;Action required for your electricity account&lt;/h2&gt;
&lt;p&gt;I have an account with Eversource, the utilities company that provides the electricity I&amp;rsquo;m using to write this, and I received this letter from another company, which is importantly &lt;em&gt;not&lt;/em&gt; Eversource.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;energy.png&#34; alt=&#34;Energy&#34;&gt;
&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Attention Eversource Customer&lt;/em&gt;
&lt;br&gt;
&lt;em&gt;Action Required by: 2019-11-30&lt;/em&gt;: this vague notice about a required action is a ploy to get you to open the letter, since you&amp;rsquo;d would want to know about any issue with your utilities immediately. The &amp;ldquo;required&amp;rdquo; action is actually just a deadline for a special rate to switch your electricity to be from clean energy. Important, but surely not required.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;offering-offers&#34;&gt;Offering offers&lt;/h2&gt;
&lt;p&gt;This one&amp;rsquo;s a classic: offering a limited time bonus for signing up and displaying it prominently.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;limited-time-offer.png&#34; alt=&#34;Limited Time Offer&#34;&gt;
&lt;img src=&#34;double-bonus-miles.JPG&#34; alt=&#34;Double Bonus Miles&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;del&gt;30,000&lt;/del&gt; 60,000&lt;/em&gt;: Double the miles! Except it&amp;rsquo;s a little unclear if the usual offer is 30,000. This is something you could easily just say, like raising a price before a &amp;ldquo;sale&amp;rdquo;.
&lt;br&gt;
&lt;em&gt;Limited time offer&lt;/em&gt;: classic. Better act fast.
&lt;br&gt;
&lt;em&gt;Float Away/photo of pool/Implying &amp;ldquo;This could be you.&amp;rdquo;&lt;/em&gt;: Yeah right, maybe if I looked good in a one-piece.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;keep-it-simple-just-tell-me-to-look-inside&#34;&gt;Keep it simple: just tell me to look inside&lt;/h2&gt;
&lt;p&gt;SoFi keeps it simple and adds some text that could easily be taken straight from a clickbait article:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;look-inside.png&#34; alt=&#34;Look Inside&#34;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;slight-hyperbole&#34;&gt;Slight hyperbole&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;most-important-catalog.JPG&#34; alt=&#34;Most Important Catalog&#34;&gt;&lt;/p&gt;
&lt;br&gt;
Coming soon on FiveThirtyEight: Christmas catalog power rankings.
&lt;hr&gt;
&lt;h2 id=&#34;discounts-and-visible-gift-cards&#34;&gt;Discounts and visible gift cards&lt;/h2&gt;
&lt;p&gt;I found this one interesting. It&amp;rsquo;s pretty common to give a special deal for signing up, and this front is no different. What sets this apart is the back of the envelope; it&amp;rsquo;s semi-translucent, and you can see the gift cards within.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;free-meals.png&#34; alt=&#34;Free Meals&#34;&gt;
&lt;img src=&#34;visible-gift-cards.JPG&#34; alt=&#34;Visible Gift Cards&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;9 Free Meals including free shipping&lt;/em&gt;: Classic free stuff, including free shipping.
&lt;br&gt;
&lt;em&gt;Take grocery shopping off your plate&lt;/em&gt;: fun meal pun, fun meal company.
&lt;br&gt;
&lt;em&gt;Visible gift cards&lt;/em&gt;: this gives an extra appeal of &amp;ldquo;I can see those and I want them.&amp;rdquo; They&amp;rsquo;re physical gift cards, which makes the discounts feel even realer.&lt;/p&gt;
&lt;p&gt;I wonder how much more expensive this is to make?&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;notable-mentions-not-featured-here&#34;&gt;Notable mentions not featured here&lt;/h2&gt;
&lt;p&gt;There are a few that I&amp;rsquo;ve gotten in the past but haven&amp;rsquo;t saved since I thought of doing this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mailing me a nickel and putting it in clear plastic so I can see it&lt;/li&gt;
&lt;li&gt;Credit card offer that looks like a check (including the tearable sides like a &lt;a href=&#34;https://www.google.com/search?tbm=isch&amp;amp;sxsrf=ACYBGNRrjTlgv-0FYhRZsz9KaUpPwvA14Q%3A1571534689750&amp;amp;source=hp&amp;amp;biw=1440&amp;amp;bih=798&amp;amp;ei=YberXYSYK8qm_Qb7go_YDA&amp;amp;q=pressure+seal+check&amp;amp;oq=pressure+seal+check&amp;amp;gs_l=img.3..35i39j0l2j0i24l2.898.2705..2835...0.0..0.154.1431.18j1......0....1..gws-wiz-img.......0i131j0i8i30.P6LZIDeYEY4&amp;amp;ved=0ahUKEwjEzLr31qnlAhVKU98KHXvBA8sQ4dUDCAY&amp;amp;uact=5&#34;&gt;pressure sealed check&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Credit card offer that was just a manila envelope with no return information&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;whats-the-point&#34;&gt;What&amp;rsquo;s the point?&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ll preface this by saying I have no familiarity with advertising by mail, so the following is all speculation.&lt;/p&gt;
&lt;p&gt;In these examples, we can see a &lt;em&gt;huge&lt;/em&gt; push just to get you to even open the letter.&lt;/p&gt;
&lt;p&gt;I would imagine that there&amp;rsquo;s a huge drop off in the recipient pipeline between receiving the letters and opening, then another big drop between opening and following through (signing up, sending donations, etc). There&amp;rsquo;s no tracking pixel that will report back that I&amp;rsquo;ve opened their letter, but I could see some A/B testing where they keep the contents the same and vary the envelope, or vice-versa, then see if the recipient was more or less likely to follow through.&lt;/p&gt;
&lt;p&gt;In that lens, I could imagine these being ways to improve the open-rate, but at what cost? Like an ad click that doesn&amp;rsquo;t end up in a sale, I could see some of these resulting in more open envelopes, but it feels like a race to the bottom. At least it gives us an interesting view into how they&amp;rsquo;re designing these envelopes to be opened.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll keep my eyes out for any interesting ones in the future. In the mean time, I&amp;rsquo;ll be here enjoying my free gifts.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MapReduce in Simple Language</title>
      <link>https://alexanderell.is/posts/simple-map-reduce/</link>
      <pubDate>Sat, 09 Feb 2019 00:00:00 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/simple-map-reduce/</guid>
      <description>&lt;h3 id=&#34;mapreduce-explained-using-the-ten-hundred-words-people-use-most-often&#34;&gt;MapReduce Explained Using the Ten Hundred Words People Use Most Often&lt;/h3&gt;
&lt;p&gt;MapReduce is a good way to take a big job that would take a long time on one computer and break it up into smaller jobs that can be done by simple computers.&lt;/p&gt;
&lt;p&gt;If you can break down a big job into smaller jobs, you can run the smaller jobs on many simple computers.  If you have a lot of simple computers, you can do a lot of the smaller jobs at the same time, and this can make your job go a lot faster.  This can also help if your computers ever break.  Instead of having to get a new big computer, if one of your simple computers breaks, you can just get another simple computer.  This can help you save money.&lt;/p&gt;
&lt;p&gt;The problem of breaking down a big job into smaller jobs is not simple though.  How do you decide how to break up the big job?  How do you decide when to send the jobs to the simple computers, and who plans it?  This can be hard.&lt;/p&gt;
&lt;p&gt;MapReduce is one way to do this.  It does the planning and the sending for you, and it lets you say what the smaller jobs look like and what to do with them.  Since it takes care of the hard parts, breaking up a big job is much easier.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;the-cat-saw-the-dog&#34;&gt;The Cat Saw The Dog&lt;/h3&gt;
&lt;p&gt;What if we were given a book and we had to count the number of times each word appears?  What if we had a lot of books to count words in?  This a good problem for MapReduce because doing it by hand would be very boring, and although a single computer could probably do it, if we had a big number of books to look through, it could take a long time.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a very simple book that is made up only of the words &lt;strong&gt;&amp;ldquo;the cat saw the dog.&amp;rdquo;&lt;/strong&gt;  Counting the words would give us this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;the: 2
cat: 1
saw: 1
dog: 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To do this in smaller jobs, we could do something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Break up the book into pages&lt;/li&gt;
&lt;li&gt;For each page, for each word, make a card with that word on it&lt;/li&gt;
&lt;li&gt;Group all the cards by word&lt;/li&gt;
&lt;li&gt;For each word pile, count how many cards are in the pile&lt;/li&gt;
&lt;li&gt;Write down the final count for each word&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;How would we give these to simple computers?  It would be nice to have each simple computer work on one page (or even smaller parts).  This means that we could break up the work between them to get it all done faster.  It would also be nice if they could do the counting for each word, because then we could break that up too.  If we&amp;rsquo;re able to break these down into small jobs, we can spread out the jobs to many simple computers, and these computers can work together to finish the big job much faster than if we had to do it on one computer (and a lot faster than doing it by hand).&lt;/p&gt;
&lt;p&gt;MapReduce is a way to break this job down into small jobs that simple computers can do easily, and it takes care of the hard parts here, which are breaking up the book into pages (step 1), grouping all the cards by words (step 3), and writing down the end count for each word (step 5).  The other hard part is making sure everything happens at the right time, which is not written down above; how do we know which simple computer to give what page, and when? How does the simple computer know what to do?  When should a computer get cards to count, and how does it get a new pile when it has finished?  MapReduce does this too, which is nice.&lt;/p&gt;
&lt;p&gt;What about steps 2 and 4?  These are the things we have to tell it about, and they are called the map and the reduce (this is why it is called MapReduce).  We just have to tell it how to do these two jobs.  It tells the simple computers how to do their jobs, and it figures out which computers do which jobs.&lt;/p&gt;
&lt;p&gt;Step 2 is the map, and it has to say what to do with what is put in and how to give the answer as pairs that can be grouped and later used by the reduce.  In the case above, you would be making a single card each time a word happens.  It takes a page, and it gives a group of pairs made up of the key (the word) and count (the card, which acts as a 1 for counting).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see that:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;#39;the cat saw the dog&amp;#39; -&amp;gt; [(&amp;#39;the&amp;#39;, 1), (&amp;#39;cat&amp;#39;, 1), (&amp;#39;saw&amp;#39;, 1), (&amp;#39;the&amp;#39;, 1), (&amp;#39;dog&amp;#39;, 1)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MapReduce then groups the words before sending them to the computers that are doing the reduce.  It knows that the first &lt;code&gt;(&#39;the&#39;, 1)&lt;/code&gt; and the second &lt;code&gt;(&#39;the&#39;, 1)&lt;/code&gt; are both for the same word, so it can group them together as the word and all of its counts: &lt;code&gt;(&#39;the&#39;, [1, 1])&lt;/code&gt;. This is like sorting the word cards into one pile for each word.&lt;/p&gt;
&lt;p&gt;Step 4 is the reduce, and it has to say what to do with all of the things that came from the map for a single key.  It takes a key (in this case, the pile&amp;rsquo;s word) and a group of things from the map (here, a pile of cards) and gives out what you want for that key (here, the number of cards in the pile).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see our reduce for a pile of two &amp;ldquo;the&amp;rdquo; cards (really, two &amp;ldquo;1&amp;rdquo; counts):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(&amp;#39;the&amp;#39;, [1, 1]) -&amp;gt; (&amp;#39;the&amp;#39;, 2)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once we had the counts, we could just write them all down and we&amp;rsquo;d be done.  This would let us break up the job and count words in a lot of books really fast.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;looking-for-things&#34;&gt;Looking for things&lt;/h3&gt;
&lt;p&gt;The word counting is not too exciting, and we could do even more interesting things.  Let&amp;rsquo;s look at how we could do something like searching for every time a word appears in a book.  This is another job that could take a while on one computer for a big number of books; let&amp;rsquo;s give it to the simple computers!&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look through this book:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;’Twas brillig, and the slithy toves
      Did gyre and gimble in the wabe:
All mimsy were the borogoves,
      And the mome raths outgrabe.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And let&amp;rsquo;s look for the lines that have the word &amp;ldquo;and.&amp;rdquo;  Here&amp;rsquo;s one way to do the map and the reduce to do this job:&lt;/p&gt;
&lt;p&gt;Map: Take in a line from the book, and if the word appears in the line, give the line and a 1 (key: line, message: 1). Since we aren&amp;rsquo;t counting, we can call this a message, because it&amp;rsquo;s more of a sign that we have it.  If the line doesn&amp;rsquo;t have the word, we don&amp;rsquo;t give anything.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;#39;Did gyre and gimble in the wabe&amp;#39; -&amp;gt; [(&amp;#39;Did gyre and gimble in the wabe&amp;#39;, 1)]
&amp;#39;All mimsy were the borogoves,&amp;#39; -&amp;gt; []
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;MapReduce then groups all of the line/message pairs, but since each line that has the word will be the key, they end up in piles of 1.  If we had two lines that are the same, we could also put their line number in the key to make sure we don&amp;rsquo;t group them together.&lt;/p&gt;
&lt;p&gt;Reduce: Take in a line and just return the line.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(&amp;#39;Did gyre and gimble in the wabe&amp;#39;, [1]) -&amp;gt; &amp;#39;Did gyre and gimble in the wabe&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What would we get at the end?  Since our map would only give out the line if the word appears in the line, only lines with that word would be sent to the reduce.  Our reduce just gives what it gets, so we&amp;rsquo;d end up with the lines that have the word in them.&lt;/p&gt;
&lt;p&gt;This would allow us to break down a large number of books into many lines and give them to many computers.  If we have a lot of computers, we could do this very quickly!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;how-many-of-you-are-there&#34;&gt;How many of you are there?&lt;/h3&gt;
&lt;p&gt;What about something like seeing how often people go on our home page and other pages?  Maybe we have a simple history of people going to pages like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;alexanderell.is           01/01/2017
alexanderell.is           01/01/2017
alexanderell.is/about     01/01/2017
alexanderell.is           01/02/2017
alexanderell.is/about     01/02/2017
alexanderell.is           01/02/2017
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It would be interesting to see how many times people went to each page.  This would be very possible with MapReduce!&lt;/p&gt;
&lt;p&gt;Map: Take in a line from the history and send out a 1 for each page (key: page, count: 1).&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;#39;alexanderell.is           01/01/2017&amp;#39; -&amp;gt; [(&amp;#39;alexanderell.is&amp;#39;, 1)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Reduce: Take in a page and the group of counts of people going to it (that MapReduce grouped for us) and give out the whole count.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(&amp;#39;alexanderell.is&amp;#39;, [1, 1, 1, 1]) -&amp;gt; (&amp;#39;alexanderell.is&amp;#39;, 4)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This would let us count it all very fast.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;why-is-this-good&#34;&gt;Why is this good?&lt;/h3&gt;
&lt;p&gt;Showing simple jobs for this can be boring, since they wouldn&amp;rsquo;t be hard to do even by hand.  It gets interesting when you&amp;rsquo;re working with big, big numbers.  What if there were more lines of the book?  What if there were a hundred books?  What if there were ten hundred?  Ten hundred times more than that?  What about ten hundred times more than &lt;em&gt;that&lt;/em&gt;? And ten times more?  That&amp;rsquo;s more books than there are people on Earth.  My computer does a lot for me, but it would take a long time to look through that many things!&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re working with a very large number of things, that&amp;rsquo;s when breaking it up and giving it to the smaller computers works the best.  MapReduce does the hard parts and lets you do just the two simple map and reduce parts, which lets you work on what you want to do and not worry about how exactly it gets done.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;simple&#34;&gt;[/simple]&lt;/h3&gt;
&lt;p&gt;I highly recommend reading through the original paper, and you can find it &lt;a href=&#34;https://research.google.com/archive/mapreduce-osdi04.pdf&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was done using the &lt;a href=&#34;https://xkcd.com/simplewriter/&#34;&gt;XKCD SimpleWriter&lt;/a&gt; with the only non-simple words being &amp;ldquo;MapReduce&amp;rdquo; and &amp;ldquo;reduce.&amp;rdquo; I couldn&amp;rsquo;t think of a good way to replace &amp;ldquo;reduce,&amp;rdquo; given that it&amp;rsquo;s in the name, but if you can think of a good way to do it, please let me know!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Good Design: Double Sided Park Signs</title>
      <link>https://alexanderell.is/posts/reservoir-signs/</link>
      <pubDate>Mon, 27 Aug 2018 23:52:22 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/reservoir-signs/</guid>
      <description>&lt;p&gt;I often walk my dog around a small reservoir near my apartment.  It&amp;rsquo;s a perfect distance for tiring a small terrier, and it&amp;rsquo;s a beautiful area.&lt;/p&gt;
&lt;p&gt;There are a number of signs around the reservoir that tell you to not swim in the water (as it&amp;rsquo;s a backup emergency water supply) and to not walk on the ice when it&amp;rsquo;s frozen over in the winter (as it&amp;rsquo;s very, very cold if you fall in). These signs are switched for the appropriate season.&lt;/p&gt;
&lt;p&gt;On one of my walks, I noticed that one of the signs was partially hidden by its support.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;back.JPG&#34; alt=&#34;The top sign says &amp;amp;ldquo;DANGER: Unsafe Ice Conditions,&amp;amp;rdquo; but it&amp;amp;rsquo;s mostly blocked by the post.&#34;&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;I remembered having seen this sign displayed in the winter, but I didn&amp;rsquo;t remember the post in front of it. Checking the reverse, I saw that the other side was the summer-time version: &amp;ldquo;No Swimming.&amp;rdquo;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;front.JPG&#34; alt=&#34;Front of the sign&#34;&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;If you have two signs, each for roughly half of the year, where do you keep the other sign when it&amp;rsquo;s not in use?  Here is a simple answer: on the back of the other one.  Switching the seasonal signs is as easy as removing it, reversing it, and bolting it back on.&lt;/p&gt;
&lt;p&gt;Every sign to be swapped can be visited with just a handful of tools.  You don&amp;rsquo;t have to worry about storing, bringing, counting, misplacing, or manufacturing the other signs.  It&amp;rsquo;s the Mass Department of Conservation and Recreation, and they want to save the extra metal!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not the cleanest looking solution, but it is simple and it works.  It seems like an obvious choice when you notice it, as good design should.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Why does a spray bottle work?</title>
      <link>https://alexanderell.is/posts/check-valve/</link>
      <pubDate>Fri, 08 Jun 2018 10:27:44 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/check-valve/</guid>
      <description>&lt;h4 id=&#34;exploring-a-simple-machine&#34;&gt;Exploring a simple machine&lt;/h4&gt;
&lt;p&gt;I took apart a simple spray bottle to see how it kept fluid flowing only in one direction from the reservoir to the nozzle.  It does so with simple check valves made of two pieces of plastic, a spring, and a small sphere.  It&amp;rsquo;s a simple way to solve a tricky problem.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;spray-bottle.png&#34; alt=&#34;Spray bottle&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;A simple spray bottle&lt;/em&gt;
&lt;/center&gt;
&lt;h4 id=&#34;one-way-flow&#34;&gt;One way flow&lt;/h4&gt;
&lt;p&gt;It&amp;rsquo;s often useful to restrict flow to only one direction.  One simple use case is in a spray bottle.  Let&amp;rsquo;s think of a simplified spray bottle with three main components: the reservoir where we store the fluid, the staging area for the fluid that&amp;rsquo;s about to be sprayed, and the nozzle that connects it to the space outside where we&amp;rsquo;ll dispense our fluid.  We want to take fluid from the reservoir, put it in the staging area, and dispense it to the outside.&lt;/p&gt;
&lt;p&gt;Fluid sounds like a technical term, but it&amp;rsquo;s an important one in this context.  Fluid refers to anything that flows, meaning its particles aren&amp;rsquo;t fixed.  This means that both gases and liquids are fluids.  We want to pay attention to particles that can move, as they&amp;rsquo;re the ones that move and push around on their own.&lt;/p&gt;
&lt;p&gt;This sounds very abstract. Here are those sections on a spray bottle:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;spray-bottle-diagram.JPG&#34; alt=&#34;Spray Bottle Diagram&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a simplified picture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;simplified.JPG&#34; alt=&#34;Simplified Spray Bottle Diagram&#34;&gt;&lt;/p&gt;
&lt;p&gt;The main user interface for the spray bottle is the trigger.  This is a simple lever with a plunger that extends into the staging area.  When the trigger is pressed, it pushes on the fluid that is in the staging area, which then travels out of the nozzle.  When the trigger is relaxed, a spring pushes it back out, creating a vacuum effect that pulls fluid from the reservoir into the staging area, allowing the cycle to continue with the next pull of the trigger.&lt;/p&gt;
&lt;h4 id=&#34;why-this-works&#34;&gt;Why this works&lt;/h4&gt;
&lt;p&gt;There are two important things happening here.  First, as the plunger applies pressure to the fluid, it must move only through the nozzle to the exterior.  If the fluid only went back to the reservoir, we would have a simple machine that draws water from its reservoir and pushes it back in.  Not very helpful.&lt;/p&gt;
&lt;p&gt;Also, when the trigger is drawn back out, it must refill the staging area from the reservoir, not from any fluid from the outside.  In the case of a water spray bottle, the outside fluid is air.  If it refilled the staging area with air, attempting to spray would just shoot the air back out of the nozzle.  This would also be a simple machine that just sprayed a small amount of air. Again, not very helpful.&lt;/p&gt;
&lt;h4 id=&#34;solving-these-problems&#34;&gt;Solving these problems&lt;/h4&gt;
&lt;p&gt;These two problems are solved with the use of simple connections that allow fluid to move in only one direction.  If we put one in between the reservoir and the staging area, we will never have to worry about fluid going from staging back to reservoir.  This solves our first issue.  If we also put one in the nozzle such that fluid can only move from staging to the nozzle, the air would never come into the staging area when we relax the trigger.&lt;/p&gt;
&lt;p&gt;Very helpful.  We can construct a very simple one-way connection using three main components: a ball, a spring, and a cylinder with a hole in one end.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;check-valve.JPG&#34; alt=&#34;Check valve&#34;&gt;&lt;/p&gt;
&lt;p&gt;The spring keeps the ball pushed against the circular hole.  This means that any fluid on the spring side doesn&amp;rsquo;t fit through the hole, as the ball is in the way.  Both the pressure from the spring and any pressure from fluid on the right pushes the ball tighter against the hole, ensuring that there&amp;rsquo;s no space for fluid to flow to the left.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s apply pressure on the left side with a plunger, which causes the fluid on the left to push against the ball.  If that pressure is more than the pressure from the right (from the spring and the right fluid), it will push the ball back, allowing some fluid from the left to escape around it through the hole.  Fluid now successfully flows from the left to the right.  When we stop applying pressure, the pressure from the right once again dominates, and the ball is pushed tightly back against the hole forming a seal.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;animation.gif&#34; alt=&#34;Animation of ball check valve&#34;&gt;&lt;/p&gt;
&lt;p&gt;Note that we&amp;rsquo;re talking about pressure from a fluid.  This pressure could be from water, oil, air, or any other fluid.  The general idea of pressure is the same for each; many small particles are bouncing against surfaces.&lt;/p&gt;
&lt;p&gt;If we&amp;rsquo;re able to apply pressure on one side, we can make it so that our fluid only flows one direction.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s put a one way connection that allows flow from the staging area to the nozzle.  When we push on water in the staging area, it will force its way through the nozzle, but when we relax the trigger, air will not come back in.  Here, the force that pushes against the spring to open the valve is the pressure created from pulling the trigger.&lt;/p&gt;
&lt;p&gt;We also want to ensure that fluid can only flow from the reservoir to the staging area. Let&amp;rsquo;s add a one-way connection there too.&lt;/p&gt;
&lt;p&gt;This also makes sense.  When we relax the trigger, we want it to pull water from the reservoir, but when we push on the same water, we don&amp;rsquo;t want it to go back to the reservoir.  Here, the force that pushes against the spring to open the valve is the pressure created from the vacuum pulling on the spring side.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;flow-diagram.JPG&#34; alt=&#34;Flow Diagram&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;Here&#39;s a flow diagram with the one way check-valves. The arrows denote the direction of the flow for each valve.&lt;/em&gt;
&lt;/center&gt;
&lt;p&gt;With those two check-valves in place, we can now exactly control the flow direction of the fluids, and we can successfully spray our water.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the check valve from the nozzle of the spray bottle.  The cylinder on the left goes over the group on the right, and the ball is pressed into the underside of the hole.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;ball-check-valve-from-spray.jpg&#34; alt=&#34;Check valve from spray bottle&#34;&gt;&lt;/p&gt;
&lt;center&gt;
&lt;em&gt;Check valve from the nozzle.  Which way should the ball point?&lt;/em&gt;
&lt;/center&gt;
&lt;h4 id=&#34;schrader-valves&#34;&gt;Schrader Valves&lt;/h4&gt;
&lt;p&gt;What if we wanted to be able to open the valve manually? This is actually the same principle behind air valves on car and bicycle tires.  Instead of a ball, a specially shaped pin is forced by a spring to form a seal.  Part of that pin extends past the valve so that you can push it from the other side of the valve.  Below is an example of a &lt;a href=&#34;https://en.wikipedia.org/wiki/Schrader_valve&#34;&gt;Schrader valves&lt;/a&gt;, and it works in a very similar manner.  Take a look at the animation; how is it similar to the ball check valve from earlier? How is it different?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://upload.wikimedia.org/wikipedia/commons/6/6c/Schrader_valve_opening_and_closing_on_a_tire.gif&#34; alt=&#34;Schrader Valve&#34;&gt;
Thanks to &lt;a href=&#34;https://en.wikipedia.org/wiki/File:Schrader_valve_opening_and_closing_on_a_tire.gif&#34;&gt;Staffanlincoln&lt;/a&gt; for the Schrader valve GIF.&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;Update: this post got quite a few comments on Hacker News, and I recommend reading through for some interesting discussion! &lt;a href=&#34;https://news.ycombinator.com/item?id=17273649&#34;&gt;https://news.ycombinator.com/item?id=17273649&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Second update: this post has been translated into Japanese if you&amp;rsquo;re interested: &lt;a href=&#34;https://gigazine.net/news/20180611-why-does-spray-bottle-work/&#34;&gt;https://gigazine.net/news/20180611-why-does-spray-bottle-work/&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Letting the User Guide You: Analyzing Store Design</title>
      <link>https://alexanderell.is/posts/store-layout/</link>
      <pubDate>Wed, 06 Jun 2018 09:02:23 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/store-layout/</guid>
      <description>&lt;p&gt;I enjoy looking at design in everyday life and the balance between how things are designed and how people try to use them.&lt;/p&gt;
&lt;p&gt;A store I visited recently had bad customer flow, and the signs were there.  There was literally a sign telling customers where to line up for the checkout line.  Customers didn&amp;rsquo;t naturally line up there, and an employee was correcting customers who missed the sign.  This is bad design.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a quick sketch of the layout of the checkout corner of the store (not quite to scale):&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;layout.JPG&#34; alt=&#34;Store layout&#34;&gt;&lt;/p&gt;
&lt;p&gt;A curved counter extended from one wall towards the back of the store.  Two cash registers (1 and 2 on the diagram) were on the bottom-right section of the curve.  There was a display of small checkout trinkets at B and calligraphy materials on the right wall at A.&lt;/p&gt;
&lt;p&gt;Where would you line up?  Where would you want customers to line up?&lt;/p&gt;
&lt;p&gt;Imagine you&amp;rsquo;re a customer seeing this for the first time.  It would be natural to line up at B after you approach the cash registers from the rest of the store.  That&amp;rsquo;s where the majority of the supplies are, so that&amp;rsquo;s probably where you spent most of your time.  You could spend your line-waiting time looking at the checkout trinkets (&amp;ldquo;I &lt;em&gt;should&lt;/em&gt; buy dog stickers for $2.99!&amp;rdquo;).  Once you check out, you could turn to the right and follow the wall to the exit.&lt;/p&gt;
&lt;p&gt;However, this store had the line start at A.  They had a &amp;ldquo;Line Starts Here&amp;rdquo; sign hanging from the ceiling.  Customers who didn&amp;rsquo;t see the sign would line up at B, and one of the employees would let them know that the line began at A.  Judging from the employee&amp;rsquo;s exasperated tone, this happened often.&lt;/p&gt;
&lt;p&gt;Customers would then walk over to A.  If there were already other customers waiting, the arriving customer would walk to the back of the store to the end of the line.  A&amp;rsquo;s corridor is only around two people wide, which meant there was congestion as people shuffled to the rear with even more if someone was looking at the calligraphy section.&lt;/p&gt;
&lt;p&gt;This is wasted energy.  The customer has to think about where to go, and their experience is affected.  If their experience is anything but easy, it&amp;rsquo;s not right.  For the employees, having to worry about how customers are lining up is time and energy that could be better spent elsewhere.&lt;/p&gt;
&lt;p&gt;Disclaimer: I was not one of the disgruntled customers.  I noticed the odd design when looking for the checkout, and the sign stood out to me.  I noticed the sequence above happen multiple times while I was waiting to check out.  Life is too short to worry about things like getting to check out first, but life is not too short to think about peoples&amp;rsquo; experiences.&lt;/p&gt;
&lt;p&gt;How can we tell this is bad design?  We can tell it&amp;rsquo;s not intuitive, as customers are not lining up at A on their own.  If it was intuitive, &lt;a href=&#34;https://99percentinvisible.org/article/norman-doors-dont-know-whether-push-pull-blame-design/&#34;&gt;you wouldn&amp;rsquo;t need to put up a sign&lt;/a&gt;.  Even with the sign it&amp;rsquo;s not natural; the employee had to tell people where to go.&lt;/p&gt;
&lt;p&gt;People are generous with their intuitions.  They will show you what they expected as they try to do that first, and you have to listen.  Seeing a user interact with a design underlines the differences between the assumptions of the designer and the assumptions of the user, and it lets you build empathy for your user.&lt;/p&gt;
&lt;p&gt;In this case, the customers are showing their assumptions and are already doing what&amp;rsquo;s natural.  Let them line up at B.  Ditch the sign. Let your employees not worry about directing, and let customers go to the rear of the store on their own if they want to wrap gifts. Keep it simple.  Design isn&amp;rsquo;t easy, but it&amp;rsquo;s not always hard.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;PS: If you can identify and tell me what store this is, I&amp;rsquo;ll buy you a beer/coffee/tea/notebook if you&amp;rsquo;re ever in Boston! Side note: the store may be in the Boston area.&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Building a Simple Cache Server in Python</title>
      <link>https://alexanderell.is/posts/simple-cache-server-in-python/</link>
      <pubDate>Sat, 26 May 2018 10:24:05 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/simple-cache-server-in-python/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m a fan of small toy projects that have the sole purpose of demonstrating a concept.  This is a small project that demonstrates how a cache server works.&lt;/p&gt;
&lt;p&gt;I think it&amp;rsquo;s important to balance toy projects with getting exposure to production-level code.  While this project has been helpful for understanding the idea, an open source project like [Squid-Cache] (&lt;a href=&#34;https://github.com/squid-cache/squid&#34;&gt;https://github.com/squid-cache/squid&lt;/a&gt;) is definitely on my reading list!&lt;/p&gt;
&lt;p&gt;At the very simplest level, an HTTP server responds to requests.  Let&amp;rsquo;s take a look at a simple server that can respond to GET requests:&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a pseudo-code for a very basic HTTP server that can respond to GET requests:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-nohighlight&#34; data-lang=&#34;nohighlight&#34;&gt;- Wait for a request
  - When a request comes in, check what file it&amp;#39;s looking for.
  - If we have it:
    - Great! Send it.
  - If we don&amp;#39;t have it:
    - Send back a 404.
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;caching&#34;&gt;Caching&lt;/h3&gt;
&lt;p&gt;At the very simplest level, a cache server is responsible for serving items that have already been requested from the main server.  Doing this gives the main server a break from having to respond to every request, and the workload can be shared by the cache server.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the pseudocode for a very basic cache server:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-nohighlight&#34; data-lang=&#34;nohighlight&#34;&gt;- Wait for a request
  - When a request comes in, check what file it&amp;#39;s looking for.
  - If we have it stored in the cache:
    - Send it.
  - If we don&amp;#39;t have it in the cache:
    - Request it from the main server
    - If the main server has it:
      - Store a copy in the cache
      - Send the file to the client
    - If the main server does not have it:
      - Send a 404 to the client
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For instance, here&amp;rsquo;s a diagram of fetching an item from a cache server when the cache server has not already saved the item and the item exists on the main server.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;block-diagram-fetch-from-server.JPG&#34; alt=&#34;Fetching item from cache server, who fetches from main server&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here are the steps for the first time an item is requested:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client requests item&lt;/li&gt;
&lt;li&gt;Cache server checks if it&amp;rsquo;s stored in the cache&lt;/li&gt;
&lt;li&gt;The item is not found&lt;/li&gt;
&lt;li&gt;The cache server requests the item from the main server&lt;/li&gt;
&lt;li&gt;The main server sends the item back&lt;/li&gt;
&lt;li&gt;The cache server saves a copy of the item&lt;/li&gt;
&lt;li&gt;The cache server sends the client the item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We could definitely swap 6 and 7, especially if we&amp;rsquo;re trying to send the item back to the client quickly.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a diagram of a subsequent request for the same item:.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;block-diagram-fetch-from-cache.JPG&#34; alt=&#34;Fetching item from cache server, who has it locally&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here are the steps for the future request:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client requests item&lt;/li&gt;
&lt;li&gt;Cache server checks if it&amp;rsquo;s stored in the cache&lt;/li&gt;
&lt;li&gt;The item is found&lt;/li&gt;
&lt;li&gt;The cache server sends the client the item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Although it looks like the main server is lonely to the right, it didn&amp;rsquo;t have to worry about the client&amp;rsquo;s request at all.  Lucky main server!&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s another way of looking at the problem.  Here, the vertical axis is time increasing as you go down, and the horizontal arrows are the requests back and forth.  The same actions are taken in the same exact order, but the actions are separated vertically by when they happen:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;caching-timeline.JPG&#34; alt=&#34;Fetching item from cache server, timeline&#34;&gt;&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t too bad! Let&amp;rsquo;s build a very simple version in python.  We&amp;rsquo;ll use &lt;a href=&#34;http://joaoventura.net/blog/2017/python-webserver/&#34;&gt;joaoventura&amp;rsquo;s simple python webserver&lt;/a&gt; as our simple http server and a template for our cache server.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how we can set the server up to abstract away the caching in the main process:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# cacheproxy.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    Implements a simple cache proxy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; socket
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; urllib.request &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; Request, urlopen, HTTPError
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; argparse
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Get port command line argument&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; argparse&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ArgumentParser()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    parser&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add_argument(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;port&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    args &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; parser&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse_args()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Define socket host and port&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SERVER_HOST &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    SERVER_PORT &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(args&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;port)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Initialize socket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server_socket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;socket(socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;AF_INET, socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SOCK_STREAM)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;setsockopt(socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SOL_SOCKET, socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;SO_REUSEADDR, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bind((SERVER_HOST, SERVER_PORT))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;listen(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Cache proxy is listening on port &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; ...&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; SERVER_PORT)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Wait for client connection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        client_connection, client_address &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; server_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;accept()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Get the client request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        request &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client_connection&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;recv(&lt;span style=&#34;color:#ae81ff&#34;&gt;1024&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(request)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Parse HTTP headers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        headers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; request&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        top_header &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; headers[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;split()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        method &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; top_header[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        filename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; top_header[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Index check&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; filename &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            filename &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/index.html&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Get the file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        content &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch_file(filename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# If we have the file, return it, otherwise 404&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; content:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HTTP/1.0 200 OK&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; content
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;HTTP/1.0 404 NOT FOUND&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; File Not Found&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Send the response and close the connection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        client_connection&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sendall(response&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;encode())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        client_connection&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Close socket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    server_socket&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Depending on how &lt;code&gt;fetch_file&lt;/code&gt; is implemented, this could just be a regular server that serves local files.  We can make this more interesting by implementing the caching at this point:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch_file&lt;/span&gt;(filename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;# Let&amp;#39;s try to read the file locally first&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    file_from_cache &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch_from_cache(filename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; file_from_cache:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Fetched successfully from cache.&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; file_from_cache
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Not in cache. Fetching from server.&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        file_from_server &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fetch_from_server(filename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; file_from_server:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            save_in_cache(filename, file_from_server)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; file_from_server
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This matches up exactly with our pseudocode. If we have it stored in the cache, return it. If we don&amp;rsquo;t have it in the cache, request it from the main server. If we get it from the main server, save a copy in the cache and return it, but if we don&amp;rsquo;t get it back from the main server, return &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since above we&amp;rsquo;re checking on the return value from this function to decide if we return a &lt;code&gt;200&lt;/code&gt; or &lt;code&gt;404&lt;/code&gt;, returning &lt;code&gt;None&lt;/code&gt; from this function will correctly return a &lt;code&gt;404&lt;/code&gt; to the client.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at what a simple &lt;code&gt;fetch_from_cache&lt;/code&gt; implementation might look like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch_from_cache&lt;/span&gt;(filename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Check if we have this file locally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; open(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cache&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; filename)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        content &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fin&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        fin&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# If we have it, let&amp;#39;s send it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; content
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IOError&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is about as simple as it gets! Check the local &lt;code&gt;cache/&lt;/code&gt; directory for the file, read it, and return it.  If we run into any &amp;ldquo;file not found&amp;rdquo; issue, we&amp;rsquo;ll just return &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, let&amp;rsquo;s take a look at a simple implementation of &lt;code&gt;fetch_from_server&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch_from_server&lt;/span&gt;(filename):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;http://127.0.0.1:8000&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; filename
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    q &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Request(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; urlopen(q)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# Grab the header and content from the server req&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        response_headers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; response&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;info()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        content &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; response&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;decode(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; content
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;except&lt;/span&gt; HTTPError:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once again, pretty simple! It creates a url to request from the main server running on &lt;code&gt;localhost:8000&lt;/code&gt;, then it attempts to get the file from that endpoint.  If it runs into a &lt;code&gt;404&lt;/code&gt; HTTPError, it simply returns &lt;code&gt;None&lt;/code&gt;, which is then passed up and results in a &lt;code&gt;404&lt;/code&gt; being returned from the cache server.&lt;/p&gt;
&lt;p&gt;Finally, let&amp;rsquo;s take a look at &lt;code&gt;save_in_cache&lt;/code&gt;, which allows us to store a local copy to serve on the next duplicate request:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;save_in_cache&lt;/span&gt;(filename, content):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Saving a copy of &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; in the cache&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;format(filename))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cached_file &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; open(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cache&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; filename, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;w&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cached_file&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;write(content)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cached_file&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;close()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Very simple! It takes the filename and content and writes it in the &lt;code&gt;cache/&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;Putting this all together, we have a simple cache server!  Here&amp;rsquo;s what it looks like when you run it for the first time:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;in-action.gif&#34; alt=&#34;Cache proxy in action&#34;&gt;&lt;/p&gt;
&lt;p&gt;Note that the main server in the top left responds to the first curl, but the cache handles the second one!&lt;/p&gt;
&lt;p&gt;How could we improve this further? First, our simple server only responds to GET requests, and it doesn&amp;rsquo;t pay any attention to cache-related headers.  Furthermore, there&amp;rsquo;s no expiration date for the files it saves, so the &lt;code&gt;index.html&lt;/code&gt; it saves today would be served from the cache forever. For showing the main functionality, this works, and we can add more complexity in the future.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/AlexanderEllis/simple-cache&#34;&gt;Here&amp;rsquo;s the full source code on GitHub.&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Investigating JavaScript&#39;s RegExp Lookbehind</title>
      <link>https://alexanderell.is/posts/regex-lookbehind/</link>
      <pubDate>Sat, 28 Apr 2018 13:22:25 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/regex-lookbehind/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Some people, when confronted with a problem, think “I know, I&amp;rsquo;ll use regular expressions.”  Now they have two problems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;http://regex.info/blog/2006-09-15/247&#34;&gt;&lt;em&gt;Jamie Zawinski&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;regular-expression-lookbehinds&#34;&gt;Regular Expression Lookbehinds&lt;/h3&gt;
&lt;p&gt;I recently came across an interesting regular expressions use case that led to an interesting look into regular expressions, JavaScript engines, and release versions.&lt;/p&gt;
&lt;p&gt;In particular, I was trying to find a certain substring from a string that did not contain a substring preceding it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say I have the strings &lt;code&gt;&#39;aabbcc&#39;&lt;/code&gt; and &lt;code&gt;&#39;bbccdd&#39;&lt;/code&gt;.  I might want to be able to locate the substring &lt;code&gt;&#39;cc&#39;&lt;/code&gt; only if it occured at some point after the substring &lt;code&gt;&#39;aa&#39;&lt;/code&gt;.  This is called a &lt;strong&gt;positive lookbehind&lt;/strong&gt;, and this would only match the &lt;code&gt;&#39;cc&#39;&lt;/code&gt; in the first string.&lt;/p&gt;
&lt;p&gt;On the other hand, if I wanted to match &lt;code&gt;&#39;cc&#39;&lt;/code&gt; only if it does not come after &lt;code&gt;&#39;aa&#39;&lt;/code&gt;, this would be a &lt;strong&gt;negative lookbehind&lt;/strong&gt;.  This would only match the &lt;code&gt;&#39;cc&#39;&lt;/code&gt; in the second string.&lt;/p&gt;
&lt;h3 id=&#34;using-lookbehinds-in-javascript&#34;&gt;Using lookbehinds in JavaScript&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re using Chrome 62 or higher, Node 6.0.0 or higher (with the &lt;code&gt;--harmony&lt;/code&gt; flag), or Node 8.1.10 or higher (see below for finding these details), you can use negative lookbehinds through Google&amp;rsquo;s V8 engine.  Here&amp;rsquo;s how you would do those cases above:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~: $ nvm use 8.10.0
Now using node v8.10.0 (npm v5.6.0)

~: $ node

// Positive lookbehind checking &amp;#39;aabbcc&amp;#39; for &amp;#39;cc&amp;#39; preceded by &amp;#39;aa&amp;#39; at some point
&amp;gt; /(?&amp;lt;=aa.*)cc/.exec(&amp;#39;aabbcc&amp;#39;)
[ &amp;#39;cc&amp;#39;, index: 4, input: &amp;#39;aabbcc&amp;#39; ]

// Positive lookbehind checking &amp;#39;bbccdd&amp;#39; for &amp;#39;cc&amp;#39; preceded by &amp;#39;aa&amp;#39; at some point
&amp;gt; /(?&amp;lt;=aa.*)cc/.exec(&amp;#39;bbccdd&amp;#39;)
null

// Negative lookbehind checking &amp;#39;aabbcc&amp;#39; for &amp;#39;cc&amp;#39; not preceded by &amp;#39;aa&amp;#39; at some point
&amp;gt; /(?&amp;lt;!aa.*)cc/.exec(&amp;#39;aabbcc&amp;#39;)
null

// Negative lookbehind checking &amp;#39;bbccdd&amp;#39; for &amp;#39;cc&amp;#39; not preceded by &amp;#39;aa&amp;#39; at some point
&amp;gt; /(?&amp;lt;!aa.*)cc/.exec(&amp;#39;bbccdd&amp;#39;)
[ &amp;#39;cc&amp;#39;, index: 2, input: &amp;#39;bbccdd&amp;#39; ]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;looking-into-compability&#34;&gt;Looking into compability&lt;/h3&gt;
&lt;p&gt;In my case, I was trying to do a negative lookbehind for my replacement.  I was also attempting to do so with just a regular expression and no additional parsing through a function, as I was passing it to another module.  After a few attempts by hand, I did some searching to see if it was possible.  The results were not encouraging, mostly indicating in older posts that it was not supported but there were many hacks to work around it.&lt;/p&gt;
&lt;p&gt;I then came across &lt;a href=&#34;http://2ality.com/2017/05/regexp-lookbehind-assertions.html&#34;&gt;2ality&amp;rsquo;s blog post on negative lookbehinds&lt;/a&gt;, which looked much more promising.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The proposal “RegExp Lookbehind Assertions” by Gorkem Yakin, Nozomu Katō, Daniel Ehrenberg is at stage 4. This blog post explains it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This post featured a great explanation, and of note, an interesting link to a Feb 2016 blog post from the Google V8 team at the bottom: &lt;a href=&#34;https://v8project.blogspot.de/2016/02/regexp-lookbehind-assertions.html&#34;&gt;V8 JavaScript Engine: RegExp lookbehind assertions&lt;/a&gt;.  The gist of that post is that the lookbehind assertions were very valuable, and although the TC39 proposal was in its early stages, it was already being rolled out in V8 version 4.9 and Chrome 49.&lt;/p&gt;
&lt;p&gt;At this point, I tried the posted examples in both my local Node (version 6.11.1) and Chrome (66).  They threw an error in Node, but worked in Chrome.  My usecase was part of a Node build script, so although the version was throwing an error, it seemed there was hope for a later version.  At this point, I could have just used the most recent version of Node or used the &lt;code&gt;--harmony&lt;/code&gt; flag, but I wanted to confirm versions to ensure that I was able to be exact about the required version for future use.&lt;/p&gt;
&lt;p&gt;I then found a more recent &lt;a href=&#34;https://v8project.blogspot.com/2017/07/upcoming-regexp-features.html&#34;&gt;blog post by the V8 team from July 2017 featuring upcoming features&lt;/a&gt;.  In particular:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Lookbehind Assertions&lt;/p&gt;
&lt;p&gt;Lookahead assertions have been part of JavaScript’s regular expression syntax from the start. Their counterpart, lookbehind assertions, are finally being introduced. Some of you may remember that this has been part of V8 for quite some time already. We even use lookbehind asserts under the hood to implement the Unicode flag specified in ES2015.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, an even more &lt;a href=&#34;https://v8project.blogspot.com/2017/09/v8-release-62.html&#34;&gt;recent V8 release post from September 2017 for V8 6.2&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;More regular expressions features&lt;/p&gt;
&lt;p&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;Lookbehind assertions, another new regular expression feature, are now available by default.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This meant that V8 6.2 had them available, which is why it worked in my Chrome 66.  The next check was to the &lt;a href=&#34;https://nodejs.org/en/download/releases/&#34;&gt;Node releases page&lt;/a&gt;.  Here&amp;rsquo;s the relevant section:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;LTS&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;V8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 8.10.0&lt;/td&gt;
&lt;td&gt;Carbon&lt;/td&gt;
&lt;td&gt;2018-03-06&lt;/td&gt;
&lt;td&gt;6.2.414.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 8.9.4&lt;/td&gt;
&lt;td&gt;Carbon&lt;/td&gt;
&lt;td&gt;2018-01-02&lt;/td&gt;
&lt;td&gt;6.1.534.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 6.0.0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2016-04-26&lt;/td&gt;
&lt;td&gt;5.0.71.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 5.12.0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2016-06-23&lt;/td&gt;
&lt;td&gt;4.6.85.32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This means that V8 version 6.2 made it into Node on March 6, 2018 in version 8.10.0, and V8 version 4.9 (allowing this with the &lt;code&gt;--harmony&lt;/code&gt; flag) made it into Node 6.0.0.  After switching to 8.10.0, my negative lookbehinds successfully ran on Node, and I was able to finish the build script.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>JavaScript: Writing your own Array.reduce</title>
      <link>https://alexanderell.is/posts/javascript-array-reduce/</link>
      <pubDate>Sun, 01 Apr 2018 09:51:00 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/javascript-array-reduce/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve found that one of the best ways for me to really understand anything is to implement it myself.  Between the ECMAScript spec and a few lines of JavaScript, you can build a chunk of knowledge for a very specific area of JavaScript.  Doing this often is a great way to gain a deeper understanding of the language, and it&amp;rsquo;s a great exercise in reading the docs, reading code, and creating your own solution.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;ll walk through &lt;code&gt;Array.reduce&lt;/code&gt; and my own implementation.  Although it&amp;rsquo;s easy to read my code, I highly recommend that you go through this process yourself.&lt;/p&gt;
&lt;p&gt;Deep knowledge of how &lt;code&gt;Array.reduce&lt;/code&gt; works is useful to have in your JavaScript toolkit.  It allows you to succinctly break down an array using a function of your choice.  The idea of reducing, especially when paired with mapping, is a common one that can be found in &lt;a href=&#34;https://en.wikipedia.org/wiki/MapReduce&#34;&gt;MapReduce&lt;/a&gt;, &lt;a href=&#34;https://redux.js.org/&#34;&gt;Redux&lt;/a&gt;, &lt;a href=&#34;https://docs.mongodb.com/manual/core/map-reduce/&#34;&gt;mongoDB&lt;/a&gt;, and many other areas.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by &lt;a href=&#34;https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduce&#34;&gt;looking at the spec&lt;/a&gt; for &lt;code&gt;Array.reduce&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;22.1.3.18 Array.prototype.reduce ( callbackfn [ , initialValue ] )

NOTE callbackfn should be a function that takes four arguments. reduce calls the callback, as a function, once for each element present in the array, in ascending order.

When the reduce method is called with one or two arguments, the following steps are taken:

1. Let O be ToObject(this value).
2. ReturnIfAbrupt(O).
3. Let len be ToLength(Get(O, &amp;#34;length&amp;#34;)).
4. ReturnIfAbrupt(len).
5. If IsCallable(callbackfn) is false, throw a TypeError exception.
6. If len is 0 and initialValue is not present, throw a TypeError exception.
7. Let k be 0.
8. If initialValue is present, then
  a. Set accumulator to initialValue.
9. Else initialValue is not present,
  a. Let kPresent be false.
  b. Repeat, while kPresent is false and k &amp;lt; len
    i. Let Pk be ToString(k).
    ii. Let kPresent be HasProperty(O, Pk).
    iii. ReturnIfAbrupt(kPresent).
    iv. If kPresent is true, then
      1. Let accumulator be Get(O, Pk).
      2. ReturnIfAbrupt(accumulator).
    v. Increase k by 1.
  c. If kPresent is false, throw a TypeError exception.
10. Repeat, while k &amp;lt; len
  a. Let Pk be ToString(k).
  b. Let kPresent be HasProperty(O, Pk).
  c. ReturnIfAbrupt(kPresent).
  d. If kPresent is true, then
    i. Let kValue be Get(O, Pk).
    ii. ReturnIfAbrupt(kValue).
    iii. Let accumulator be Call(callbackfn, undefined, «accumulator, kValue, k, O»).
    iv. ReturnIfAbrupt(accumulator).
  e. Increase k by 1.
11. Return accumulator.

The length property of the reduce method is 1.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Overall it&amp;rsquo;s not too bad!  Let&amp;rsquo;s write this out in pseudocode:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. Keep track of input object with O
2. If there are any issues doing so, return
3. Keep track of the length of the input object with variable len
4. If there are any issues doing so, return
5. If we can&amp;#39;t call callbackfn, eg it isn&amp;#39;t a function, throw a TypeError exception (we need a function to call)
6. If we have an empty input but don&amp;#39;t have an initial value, throw a TypeError exception (we need to have a value to start with)
7. Initialize a counter variable k to be 0
8. If we have an initial value,
  a. Initialize our accumulator variable to be that initial value
9. If we don&amp;#39;t have an initial value,
  a. Initialize a present indicator variable kPresent to be false
  b. While we haven&amp;#39;t found an item and our counter variable is less than the length of our input,
    i. Get the string version of our counter variable to use for indexing
    ii. Check whether or not our input object O has an element at the current index
    iii. If there&amp;#39;s any problem indexing in to our input element, return
    iv. If our input object O has an element at the current index,
      1. Initialize our accumulator variable to be that element
      2. If there&amp;#39;s any problem accessing that element, return
    v. Increase our counter variable to the next index
  c. If we&amp;#39;ve gone through all the indexes of the input object and didn&amp;#39;t find a value, throw a TypeError exception (since we need a value to start with)
10. While our counter variable k is less than the length of our input object,
  a. Get the string version of our counter variable to use for indexing
  b. Check whether or not our input object O has an element at the current index
  c. If there&amp;#39;s any problem checking, return
  d. If there is an element at the current index,
    i. Assign that element to a variable kValue
    ii. If there&amp;#39;s a problem accessing or assigning, return
    iii. Call our callback function with the accumulator variable, the current element kValue, the current index k, and our input O, then assign that value to the accumulator variable
    iv. If there was any problem calling the callback, return
  e. Increase our counter variable by one
11. We&amp;#39;re done! Return the accumulator variable
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That ended up much longer than it needs to be, in part because the specification is very specific.  Let&amp;rsquo;s put this in broad brush strokes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1. Make sure we have an object with length, a callback function, and a value to start with
2. Loop through the rest of the values and update the accumulator variable with the result of our callback function called with the accumulator and the value
3. Return the accumulator variable
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That doesn&amp;rsquo;t look so bad!  If you&amp;rsquo;re looking for an interesting exercise, try writing your own reduce function.  Here&amp;rsquo;s one way to do it in JavaScript:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;function reduce (input, callback, initialValue) {
  let O = input;
  const inputLength = O.length;


  if (typeof callback !== &amp;#39;function&amp;#39;) {
    throw new TypeError(&amp;#39;callback must be a function&amp;#39;);
  }

  if (!inputLength &amp;amp;&amp;amp; !initialValue &amp;amp;&amp;amp; initialValue !== 0) {
    throw new TypeError(&amp;#39;We need an initial value&amp;#39;);
  }

  let k = 0;

  let accumulator;
  let Pk;
  let kPresent;

  if (initialValue) {
    accumulator = initialValue;
  } else {
    kPresent = false;
    while (!kPresent &amp;amp;&amp;amp; k &amp;lt; inputLength) {
      Pk = String(k);
      kPresent = O.hasOwnProperty(Pk);

      if (kPresent) {
        accumulator = O[Pk];
      }
      k++;
    }

    if (!kPresent) {
      throw new TypeError(&amp;#39;If no initial value is given, the input must have a value to use as the initial value&amp;#39;);
    }
  }

  while (k &amp;lt; inputLength) {
    Pk = String(k);
    kPresent = O.hasOwnProperty(Pk);

    if (kPresent) {
      let kValue = O[Pk];
      accumulator = callback(accumulator, kValue, k, O);
    }

    k++;
  }

  return accumulator;
}


console.log(reduce([1, 2, 3], (total, current) =&amp;gt; total + current));
&amp;gt; 6

console.log(reduce([1, 2, 3], (total, current) =&amp;gt; total + current, 10));
&amp;gt; 16
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Success!&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve written your own, I highly recommend comparing to both my version and the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Polyfill&#34;&gt;MDN Polyfill version&lt;/a&gt;.  What&amp;rsquo;s different? What choices did you make that are different?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>typeof null: investigating a classic JavaScript bug</title>
      <link>https://alexanderell.is/posts/typeof-null/</link>
      <pubDate>Mon, 19 Mar 2018 08:40:17 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/typeof-null/</guid>
      <description>&lt;p&gt;In my last post, I looked into some JavaScript casting and explored why &lt;code&gt;0 &amp;lt;= null&lt;/code&gt; evaluates as true.&lt;/p&gt;
&lt;p&gt;For this post, I&amp;rsquo;d like to investigate another unexpected behavior in JavaScript: why &lt;code&gt;typeof(null)&lt;/code&gt; evaluates as &lt;code&gt;&#39;object&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof&#34;&gt;well-known bug&lt;/a&gt;, and we&amp;rsquo;ll investigate first in the ECMAScript specification followed by a deep dive into an early implementation of JavaScript to see the bug in its natural habitat.&lt;/p&gt;
&lt;p&gt;The main idea is that the code assigned each item some bits for use as flags for different types, but &lt;em&gt;null&lt;/em&gt; was different.  Objects had a flag of &lt;code&gt;000&lt;/code&gt;, so an object&amp;rsquo;s last 3 bits were &lt;code&gt;000&lt;/code&gt;.  &lt;em&gt;null&lt;/em&gt; was previously defined as 32 &lt;code&gt;0&lt;/code&gt; bits: &lt;code&gt;00000000000000000000000000000000&lt;/code&gt;.  When the code tried to check &lt;em&gt;null&lt;/em&gt;&amp;rsquo;s flag, the last three bits of &lt;em&gt;null&lt;/em&gt; (&lt;code&gt;000&lt;/code&gt;) matched the Object flag (&lt;code&gt;000&lt;/code&gt;), so it was incorrectly determined to be an object.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a deeper look!&lt;/p&gt;
&lt;p&gt;As before, for a proper background and preparation for this post, please watch (or rewatch!) &lt;a href=&#34;https://www.destroyallsoftware.com/talks/wat&#34;&gt;wat&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;To start, let&amp;rsquo;s take a look at the &lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-typeof-operator&#34;&gt;ECMAScript spec for typeof&lt;/a&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;The typeof Operator

Runtime Semantics: Evaluation

UnaryExpression : typeof UnaryExpression

1. Let val be the result of evaluating UnaryExpression.
2. If Type(val) is Reference, then
  a. If IsUnresolvableReference(val) is true, return &amp;#34;undefined&amp;#34;.
3. Set val to ? GetValue(val).
4. Return a String according to Table 35.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So when we call &lt;code&gt;typeof null&lt;/code&gt;, &lt;em&gt;null&lt;/em&gt; is passed in as the UnaryExpression.  It is evaluated in step 1, and val gets its value, &lt;em&gt;null&lt;/em&gt;.  Type(null) evaluates to the &lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-terms-and-definitions-null-type&#34;&gt;Null type&lt;/a&gt;, so Type(val) is not Reference and step 2 is false.  Next, step 3 calls GetValue on &lt;em&gt;null&lt;/em&gt;.  We saw get value before, but let&amp;rsquo;s take another look to confirm nothing odd is going on:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;GetValue ( V )

1. ReturnIfAbrupt(V).
2. If Type(V) is not Reference, return V.
3. Let base be GetBase(V).
4. If IsUnresolvableReference(V) is true, throw a ReferenceError exception.
5. If IsPropertyReference(V) is true, then
  a. If HasPrimitiveBase(V) is true, then
    i. Assert: In this case, base will never be undefined or null.
    ii. Set base to ! ToObject(base).
  b. Return ? base.[[Get]](GetReferencedName(V), GetThisValue(V)).
6. Else base must be an Environment Record,
  a. Return ? base.GetBindingValue(GetReferencedName(V), IsStrictReference(V)) (see 8.1.1).
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Calling GetValue with &lt;em&gt;null&lt;/em&gt; will only reach the second step, as Type(null) is the Null type, and GetValue will return &lt;em&gt;null&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Finally we reach step 4: &amp;ldquo;Return a String according to Table 35.&amp;rdquo;  Here&amp;rsquo;s Table 35:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type of val&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Undefined&lt;/td&gt;
&lt;td&gt;&amp;ldquo;undefined&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Null&lt;/td&gt;
&lt;td&gt;&amp;ldquo;object&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;&amp;ldquo;boolean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;&amp;ldquo;number&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;&amp;ldquo;string&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Symbol&lt;/td&gt;
&lt;td&gt;&amp;ldquo;symbol&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object (ordinary and does not implement [[Call]])&lt;/td&gt;
&lt;td&gt;&amp;ldquo;object&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object (standard exotic and does not implement [[Call]])&lt;/td&gt;
&lt;td&gt;&amp;ldquo;object&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object (implements [[Call]])&lt;/td&gt;
&lt;td&gt;&amp;ldquo;function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object (non-standard exotic and does not implement [[Call]])&lt;/td&gt;
&lt;td&gt;Implementation-defined. Must not be &amp;ldquo;undefined&amp;rdquo;, &amp;ldquo;boolean&amp;rdquo;, &amp;ldquo;function&amp;rdquo;, &amp;ldquo;number&amp;rdquo;, &amp;ldquo;symbol&amp;rdquo;, or &amp;ldquo;string&amp;rdquo;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Not very illuminating.  It says that &lt;code&gt;typeof null&lt;/code&gt; should just return the string &lt;code&gt;&#39;object&#39;&lt;/code&gt;.  But why?  Let&amp;rsquo;s dive a little deeper.&lt;/p&gt;
&lt;p&gt;I came across the &lt;a href=&#34;http://2ality.com/2013/10/typeof-null.html&#34;&gt;2ality post about the history of &lt;code&gt;typeof null&lt;/code&gt;&lt;/a&gt;, and it answers the question very well with reference to an early implementation.  As Brendan Eich wrote in the top comment, the code comes from the 1996 Spider Monkey implementation, not the very original 10-day Mocha, although the bug existed in the original as well.  Let&amp;rsquo;s take a look!&lt;/p&gt;
&lt;p&gt;Although the link from that post is deprecated, we can still see the classic version of the code on &lt;a href=&#34;https://dxr.mozilla.org/classic/source/js/src/&#34;&gt;https://dxr.mozilla.org/classic/source/js/src/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Two main C files will be of interest here at first: &lt;code&gt;classic/js/src/jsapi.h&lt;/code&gt; and &lt;code&gt;classic/js/src/jsapi.c&lt;/code&gt;.  Let&amp;rsquo;s take a look at the relevant part of &lt;code&gt;jsapi.c&lt;/code&gt; first:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
    JSType type = JSTYPE_VOID;
    JSObject *obj;
    JSObjectOps *ops;
    JSClass *clasp;

    CHECK_REQUEST(cx);
    if (JSVAL_IS_VOID(v)) {
        type = JSTYPE_VOID;
    } else if (JSVAL_IS_OBJECT(v)) {
        obj = JSVAL_TO_OBJECT(v);
        if (obj &amp;amp;&amp;amp;
            (ops = obj-&amp;gt;map-&amp;gt;ops,
             ops == &amp;amp;js_ObjectOps
             ? (clasp = OBJ_GET_CLASS(cx, obj),
                clasp-&amp;gt;call || clasp == &amp;amp;js_FunctionClass)
             : ops-&amp;gt;call != 0)) {
            type = JSTYPE_FUNCTION;
        } else {
            type = JSTYPE_OBJECT;
        }
    } else if (JSVAL_IS_NUMBER(v)) {
        type = JSTYPE_NUMBER;
    } else if (JSVAL_IS_STRING(v)) {
        type = JSTYPE_STRING;
    } else if (JSVAL_IS_BOOLEAN(v)) {
        type = JSTYPE_BOOLEAN;
    }
    return type;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s walk through what this does.&lt;/p&gt;
&lt;p&gt;First, it checks &lt;code&gt;JSVAL_IS_VOID(v)&lt;/code&gt;.  That is defined in &lt;code&gt;jsapi.h&lt;/code&gt; as follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define JSVAL_IS_VOID(v)        ((v) == JSVAL_VOID)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And &lt;code&gt;JSVAL_VOID&lt;/code&gt; is defined as:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So the void value, what we know in JavaScript as &lt;code&gt;undefined&lt;/code&gt;, is defined as &lt;code&gt;-(2^30)&lt;/code&gt;, an integer outside of the integer range.  The &lt;code&gt;typeof&lt;/code&gt; call first checks if that&amp;rsquo;s what we&amp;rsquo;re dealing with.  If so, the line &lt;code&gt;type = JSVAL_VOID&lt;/code&gt; is called, and then that is returned.  Good so far!&lt;/p&gt;
&lt;p&gt;If we aren&amp;rsquo;t dealing with a void value, the code then evaluates &lt;code&gt;JSVAL_IS_OBJECT(v)&lt;/code&gt;.  This is defined as follows in &lt;code&gt;jsapi.h&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define JSVAL_IS_OBJECT(v)      (JSVAL_TAG(v) == JSVAL_OBJECT)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So this checks a tag on the value and sees if it matches the object tag.  Here are the tag definitions from &lt;code&gt;jsapi.h&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
 * Type tags stored in the low bits of a jsval.
 */
#define JSVAL_OBJECT            0x0     /* untagged reference to object */
#define JSVAL_INT               0x1     /* tagged 31-bit integer value */
#define JSVAL_DOUBLE            0x2     /* tagged reference to double */
#define JSVAL_STRING            0x4     /* tagged reference to string */
#define JSVAL_BOOLEAN           0x6     /* tagged boolean value */
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This means that objects will have &lt;code&gt;000&lt;/code&gt; as the bits for its tag, integers will have &lt;code&gt;001&lt;/code&gt;, doubles will have &lt;code&gt;010&lt;/code&gt;, strings will have &lt;code&gt;100&lt;/code&gt;, and booleans will have &lt;code&gt;110&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s &lt;code&gt;JSVAL_TAG&lt;/code&gt; and its called &lt;code&gt;JSVAL_TAGMASK&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define JSVAL_TAGBITS           3
#define JSVAL_TAGMASK           JS_BITMASK(JSVAL_TAGBITS)
#define JSVAL_TAG(v)            ((v) &amp;amp; JSVAL_TAGMASK)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;JS_BITMASK&lt;/code&gt; comes from a different file, &lt;a href=&#34;https://dxr.mozilla.org/classic/source/js/src/jstypes.h&#34;&gt;jstypes.h&lt;/a&gt;, and it builds the correct value to bitmask against tag.  This means that it grabs the last 3 bits from the value, and those three bits correspond to the tag.&lt;/p&gt;
&lt;p&gt;For instance, if we were working with a very simplified version with integers of 5 bit length and the last 3 bits were the tag, we may have a value like 00001001.  Here, the first 5 bits, &lt;strong&gt;00001&lt;/strong&gt;, would correspond to the value, and the last 3 bits, &lt;strong&gt;001&lt;/strong&gt;, wold be the tag (here, for an integer).  To get just the tag, we would do a bitwise AND comparison between our value &lt;code&gt;00001001&lt;/code&gt; and a mask with 1s for the last three bits, &lt;code&gt;111&lt;/code&gt;, to get just the tag, &lt;code&gt;001&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Value:           00001001
Bitmask:         00000111
Value &amp;amp; bitmask: 00000001
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So far so good!  Let&amp;rsquo;s see what we can find about the &lt;em&gt;null&lt;/em&gt; value.  A quick search reveals the definition for &lt;em&gt;null&lt;/em&gt; from &lt;code&gt;jsapi.h&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
 * Well-known JS values.  The extern&amp;#39;d variables are initialized when the
 * first JSContext is created by JS_NewContext (see below).
 */
#define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
#define JSVAL_NULL              OBJECT_TO_JSVAL(0)
#define JSVAL_ZERO              INT_TO_JSVAL(0)
#define JSVAL_ONE               INT_TO_JSVAL(1)
#define JSVAL_FALSE             BOOLEAN_TO_JSVAL(JS_FALSE)
#define JSVAL_TRUE              BOOLEAN_TO_JSVAL(JS_TRUE)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;JSVAL_NULL&lt;/code&gt; is &lt;code&gt;OBJECT_TO_JSVAL(0)&lt;/code&gt;.  Let&amp;rsquo;s follow this down:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jsapi.h&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define OBJECT_TO_JSVAL(obj)    ((jsval)(obj))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;jspubtd.h&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;typedef jsword    jsval;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;jscompat.h&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;typedef JSWord jsword;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;jstypes.h&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
** A JSWord is an integer that is the same size as a void*
*/
typedef long JSWord;
typedef unsigned long JSUword;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This means that &lt;em&gt;null&lt;/em&gt;, &lt;code&gt;OBJECT_TO_JSVAL(0)&lt;/code&gt;, is actually just creating a &lt;code&gt;long&lt;/code&gt; with just &lt;code&gt;0&lt;/code&gt; as the value.  This is just &lt;code&gt;00000000000000000000000000000000&lt;/code&gt; (32 bits, all 0).&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s go back to our &lt;code&gt;JSVAL_IS_OBJECT&lt;/code&gt; call&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define JSVAL_IS_OBJECT(v)      (JSVAL_TAG(v) == JSVAL_OBJECT)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, what happens if we call JS_TAG on our &lt;em&gt;null&lt;/em&gt; value?&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Value:           00000000000000000000000000000000
Bitmask:         00000000000000000000000000000111
Value &amp;amp; bitmask: 00000000000000000000000000000000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No surprises there: this means that the resulting calculated tag is just &lt;code&gt;0x0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is where we have the problem!  We then compare &lt;em&gt;null&lt;/em&gt;&amp;rsquo;s calculated &lt;code&gt;0x0&lt;/code&gt; tag with &lt;code&gt;JSVAL_OBJECT&lt;/code&gt;, &lt;code&gt;0x0&lt;/code&gt;.  They match!&lt;/p&gt;
&lt;p&gt;This means our JS_TypeOfValue call then checks if its a function, and when it isn&amp;rsquo;t, it assigns &lt;code&gt;type&lt;/code&gt; to be &lt;code&gt;JSTYPE_OBJECT&lt;/code&gt; and returns &lt;code&gt;type&lt;/code&gt;.  &lt;code&gt;JSTYPE_OBJECT&lt;/code&gt; is the string &lt;code&gt;&#39;object&#39;&lt;/code&gt;, and we get &lt;code&gt;typeof null === &#39;object&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As mentioned in the 2ality blog, interestingly enough there &lt;em&gt;is&lt;/em&gt; a function (in this version of the code, at least) that checks for a &lt;em&gt;null&lt;/em&gt; value:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define JSVAL_IS_NULL(v)        ((v) == JSVAL_NULL)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Maybe this was added in after the fact?  Maybe this was in there and overlooked?  Regardless, this bug was in the original, and it remains to this day.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;In general, typeof seems like a mess that will be hard to reform sensibly. &amp;ldquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;— &lt;em&gt;Brendan Eich 2006/03/31 15:13&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://brendaneich.com/&#34;&gt;Brendan Eich&lt;/a&gt; for writing JavaScript, Mozilla for hosting the classic code, and  &lt;a href=&#34;http://2ality.com/p/about.html&#34;&gt;Dr. Axel Rauschmayer&lt;/a&gt; for the very helpful blog post about this topic.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Investigating JavaScript Casting Behavior</title>
      <link>https://alexanderell.is/posts/javascript-casting/</link>
      <pubDate>Sun, 18 Mar 2018 11:14:19 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/javascript-casting/</guid>
      <description>&lt;p&gt;The disconnect between &amp;ldquo;what seems like it should happen&amp;rdquo; and &amp;ldquo;what happens&amp;rdquo; causes you to push doors that are to be pulled, tap ads that load in the place of content, and cast &lt;code&gt;null&lt;/code&gt; into 0 by accident in JavaScript.&lt;/p&gt;
&lt;p&gt;For more on JavaScript&amp;rsquo;s unintuitive behavior, please watch (or rewatch) &lt;a href=&#34;https://www.destroyallsoftware.com/talks/wat&#34;&gt;wat&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Much of this behavior can be explained by JavaScript&amp;rsquo;s casting.  From ECMAScipt: &amp;ldquo;The ECMAScript language implicitly performs automatic type conversion as needed.&amp;rdquo;  Since knowing is half the battle, I&amp;rsquo;ve found it helpful to know of the unexpected behavior so that it&amp;rsquo;s in the back of my mind when writing new code.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I&amp;rsquo;ve worked with and am reasonably comfortable with JavaScript, but occasionally unintuitive behavior will still show up unannounced.  For instance, I recently ran into the following unexpected code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; 0 == null
false
&amp;gt; 0 === null
false
&amp;gt; 0 &amp;lt; null
false
&amp;gt; 0 &amp;lt;= null
true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At first glance, not knowing the exact casting behavior, this seems to make no sense at all (especially without an idea of what&amp;rsquo;s going on under the hood).  If 0 isn&amp;rsquo;t equal to null, either with equality or strict equality, and 0 is not less than null, why would &lt;code&gt;0 &amp;lt;= null&lt;/code&gt; be true?&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-relational-operators-runtime-semantics-evaluation&#34;&gt;Diving into the ECMAScript docs&lt;/a&gt; tells us more:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;RelationalExpression:RelationalExpression&amp;lt;=ShiftExpression

1) Let lref be the result of evaluating RelationalExpression.
2) Let lval be ? GetValue(lref).
3) Let rref be the result of evaluating ShiftExpression.
4) Let rval be ? GetValue(rref).
5) Let r be the result of performing Abstract Relational Comparison rval &amp;lt; lval with LeftFirst equal to false.
6) ReturnIfAbrupt(r).
7) If r is true or undefined, return false. Otherwise, return true.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So this evaluates the left side, gets the value, evaluates the right side, gets the value, and performs &lt;em&gt;&lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-abstract-relational-comparison&#34;&gt;Abstract Relational Comparison rval &amp;lt; lval with LeftFirst equal to false&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Note how the right and left values are swapped in the Abstract Relational Comparison.  This uses the idea that &lt;code&gt;a &amp;lt;= b&lt;/code&gt; is the same as &lt;code&gt;!(b &amp;lt; a)&lt;/code&gt;.  If &lt;code&gt;b&lt;/code&gt; is not less than &lt;code&gt;a&lt;/code&gt;, it must be greater than or equal to &lt;code&gt;a&lt;/code&gt;, or flipped, &lt;code&gt;a&lt;/code&gt; must be less than or equal to &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at the Abstract Relational Comparison x &amp;lt; y:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Abstract Relational Comparison x &amp;lt; y

1. If the LeftFirst flag is true, then
    a. Let px be ? ToPrimitive(x, hint Number).
    b. Let py be ? ToPrimitive(y, hint Number).

2. Else the order of evaluation needs to be reversed to preserve left to right evaluation,
    a. Let py be ? ToPrimitive(y, hint Number).
    b. Let px be ? ToPrimitive(x, hint Number).

3. If both px and py are Strings, then
    a. If py is a prefix of px, return false. (A String value p is a prefix of String value q if q can be the result of concatenating p and some other String r. Note that any String is a prefix of itself, because r may be the empty String.)
    b. If px is a prefix of py, return true.
    c. Let k be the smallest nonnegative integer such that the code unit at index k within px is different from the code unit at index k within py. (There must be such a k, for neither String is a prefix of the other.)
    d. Let m be the integer that is the code unit value at index k within px.
    e. Let n be the integer that is the code unit value at index k within py.
    f. If m &amp;lt; n, return true. Otherwise, return false.

4. Else,
    a. Let nx be ? ToNumber(px). Because px and py are primitive values evaluation order is not important.
    b. Let ny be ? ToNumber(py).
    c. If nx is NaN, return undefined.
    d. If ny is NaN, return undefined.
    e. If nx and ny are the same Number value, return false.
    f. If nx is +0 and ny is -0, return false.
    g. If nx is -0 and ny is +0, return false.
    h. If nx is +∞, return false.
    i. If ny is +∞, return true.
    j. If ny is -∞, return false.
    k. If nx is -∞, return true.
    l. If the mathematical value of nx is less than the mathematical value of ny —note that these mathematical values are both finite and not both zero—return true. Otherwise, return false.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For our case comparing &lt;code&gt;0 &amp;lt;= null&lt;/code&gt;, the inputs are evaluated in the second step (as LeftFirst was passed in as false) with &lt;strong&gt;ToPrimitive&lt;/strong&gt; with a hint of &lt;code&gt;Number&lt;/code&gt;.  Since they aren&amp;rsquo;t both strings, step 3 is skipped, and and later &lt;strong&gt;ToNumber&lt;/strong&gt; is called on them in step 4.  This seems promising!&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at &lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-toprimitive&#34;&gt;ToPrimitive&lt;/a&gt; to see what&amp;rsquo;s happening here:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ToPrimitive ( input [ , PreferredType ] )

1. Assert: input is an ECMAScript language value.
2. If Type(input) is Object, then

    a. If PreferredType was not passed, let hint be &amp;#34;default&amp;#34;.
    b. Else if PreferredType is hint String, let hint be &amp;#34;string&amp;#34;.
    c. Else PreferredType is hint Number, let hint be &amp;#34;number&amp;#34;.
    d. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
    e. If exoticToPrim is not undefined, then
        i. Let result be ? Call(exoticToPrim, input, « hint »).
        ii. If Type(result) is not Object, return result.
        iii. Throw a TypeError exception.

    f. If hint is &amp;#34;default&amp;#34;, set hint to &amp;#34;number&amp;#34;.
    g. Return ? OrdinaryToPrimitive(input, hint).

3. Return input.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I expected the &lt;code&gt;Number&lt;/code&gt; hint to be the smoking gun after seeing it before (and referenced on StackOverflow), but I don&amp;rsquo;t think that&amp;rsquo;s what&amp;rsquo;s causing this behavior.  When &lt;code&gt;null&lt;/code&gt; is passed to &lt;code&gt;ToPrimitive&lt;/code&gt;, the first step correctly asserts that it&amp;rsquo;s a language value.  The second step conditional, however does not evaluate as true, as null is &lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-ecmascript-data-types-and-values&#34;&gt;&lt;strong&gt;the Null type&lt;/strong&gt;&lt;/a&gt;, not the Object type.  (TODO: future blog post on why &lt;code&gt;typeof(null)&lt;/code&gt; evaluates to &lt;code&gt;&#39;object&#39;&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;So it actually looks like ToPrimitive immediate goes to step 3, where it just returns &lt;code&gt;null&lt;/code&gt;.  This means we can look back at the 4th step in Abstract Relational Comparison.  The next call is &lt;code&gt;Let nx be ? ToNumber(px)&lt;/code&gt;.  This is where the magic is happening.  &lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-tonumber&#34;&gt;&lt;strong&gt;ToNumber&lt;/strong&gt;&lt;/a&gt; does what you&amp;rsquo;d (mostly) expect and returns &lt;code&gt;+0&lt;/code&gt; for an argument type of &lt;code&gt;Null&lt;/code&gt;, which is what we&amp;rsquo;ve passed in with &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is finally where we&amp;rsquo;re comparing 0 and +0.  +0 is not less than 0, so our call to Abstract Relational Comparison returns false, and our original &amp;lt;= RelationalExpression returns true, evaluating &lt;code&gt;0 &amp;lt;= null&lt;/code&gt; as true.&lt;/p&gt;
&lt;p&gt;Returning to our original troublesome code, we can connect why this leads to our &lt;code&gt;0 &amp;lt;= null&lt;/code&gt; being true.  The only things left to look at are the &lt;a href=&#34;https://www.ecma-international.org/ecma-262/8.0/index.html#sec-abstract-relational-comparison&#34;&gt;&lt;strong&gt;equality comparisons&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Abstract Equality Comparison

The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:

1. If Type(x) is the same as Type(y), then
    a. Return the result of performing Strict Equality Comparison x === y.
2. If x is null and y is undefined, return true.
3. If x is undefined and y is null, return true.
4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
8. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
9. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.
10. Return false.
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Strict Equality Comparison

The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:

1. If Type(x) is different from Type(y), return false.
2. If Type(x) is Number, then
    a. If x is NaN, return false.
    b. If y is NaN, return false.
    c. If x is the same Number value as y, return true.
    d. If x is +0 and y is -0, return true.
    e. If x is -0 and y is +0, return true.
    f. Return false.
3. Return SameValueNonNumber(x, y).
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For the Abstract Equality, we can see that there is no ToNumber call that would make &lt;code&gt;null&lt;/code&gt; +0, and we can also see that the Strict Equality immediately returns false when Type(0), &lt;strong&gt;Number&lt;/strong&gt;, is different from Type(null), &lt;strong&gt;Null&lt;/strong&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; 0 == null
false // No casting in Abstract Equality
&amp;gt; 0 === null
false // No casting in Strict Equality
&amp;gt; 0 &amp;lt; null
false // Similar casting as above, and 0 &amp;lt; +0 is false
&amp;gt; 0 &amp;lt;= null
true // Main casting question, and 0 &amp;lt;= +0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Definitely tricky, but good to keep in the back of your mind!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Investigating a Bug in Moment.js</title>
      <link>https://alexanderell.is/posts/investigating-moment-bug/</link>
      <pubDate>Sat, 06 Jan 2018 11:37:37 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/investigating-moment-bug/</guid>
      <description>&lt;h3 id=&#34;investigating-a-bug-in-momentjs&#34;&gt;Investigating a bug in Moment.js&lt;/h3&gt;
&lt;p&gt;In this post, I walk through the steps I took while investigating a bug in Moment.js, an open source JavaScript library for dealing with dates and times.&lt;/p&gt;
&lt;p&gt;Rather than write about what would have ideally happened (the steps of which would look like 1) see bug, 2) know issue, 3) investigate fix, 4) open PR), I wanted to include the entire process, which included some roundabout work.  When reading about someone else&amp;rsquo;s work, I&amp;rsquo;ve found that it&amp;rsquo;s very valuable to see both what worked &lt;em&gt;and&lt;/em&gt; what didn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;One of my recent goals has been to contribute more to open source.  OSS makes my life easier, and the least I can do is to give back with small bug fixes or documentation updates.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used Moment.js a number of times, both for side projects and for my day job.  Recently, I&amp;rsquo;ve been occasionally visiting the issues page to see if there was anything I could help out with.  Since I work with JavaScript a lot, I figured something like this would be the most approachable for me.  I also wanted to learn about how it works internally, and I knew that this would be a great chance to dig through the code and learn more.&lt;/p&gt;
&lt;h4 id=&#34;the-issue&#34;&gt;The issue&lt;/h4&gt;
&lt;p&gt;The other day, I came across &lt;a href=&#34;https://github.com/moment/moment/issues/4386&#34;&gt;this issue&lt;/a&gt; marked as a bug:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Hello,
I&amp;#39;m reviewing tutorial
https://momentjs.com/docs/#/i18n/locale-data/

I got an issue for the following:
localeData = moment.localeData();
localeData.longDateFormat();
Uncaught TypeError: Cannot read property &amp;#39;toUpperCase&amp;#39; of undefined

Also recently found.
localeData.relativeTime();
Uncaught TypeError: Cannot read property &amp;#39;replace&amp;#39; of undefined

localeData.week();
Uncaught TypeError: Cannot read property &amp;#39;year&amp;#39; of undefined

Please resolve this issue.
Thank you.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This looked like something I could investigate!  I wasn&amp;rsquo;t sure if I could completely fix it or if it was a good first bug, but I decided to see what I could do with it.&lt;/p&gt;
&lt;p&gt;I started by reproducing the issue on a jsfiddle:&lt;/p&gt;
&lt;script async src=&#34;//jsfiddle.net/efe9L98p/1/embed/&#34;&gt;&lt;/script&gt;
&lt;p&gt;If you click on Result and check your console, you&amp;rsquo;ll see the TypeError.  This was good to confirm that the error existed.  I next confirmed the error appeared for &lt;code&gt;relativeTime()&lt;/code&gt; and for &lt;code&gt;week()&lt;/code&gt;.  Both of those appeared as well, so I was confident the error was real.  I also checked the linked Moment docs and confirmed that the method signatures at that link were indeed &lt;code&gt;localeData.longDateFormat()&lt;/code&gt;, &lt;code&gt;localeData.relativeTime()&lt;/code&gt;, and &lt;code&gt;localeData.week()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(At this point, how would you debug further?  I missed a few things here that seem obvious in retrospect and went on a longer route, and I&amp;rsquo;ll review the different paths towards the end.)&lt;/p&gt;
&lt;p&gt;I then forked and cloned the Moment repository so that I could have a local copy.  This let me dig around in the code and search for specific things.&lt;/p&gt;
&lt;p&gt;As a quick check, I searched for a few things using &lt;code&gt;grep&lt;/code&gt; to see how often they came up.  &lt;code&gt;localeData&lt;/code&gt;, &lt;code&gt;.replace&lt;/code&gt;, and &lt;code&gt;.year&lt;/code&gt; appeared often, but &lt;code&gt;toUpperCase()&lt;/code&gt; appeared only twice:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;moment: $ grep -r toUpperCase src
src/lib/locale/formats.js:        formatUpper = this._longDateFormat[key.toUpperCase()];
src/test/moment/normalize_units.js:        fullKeyCaps = fullKey.toUpperCase();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Two results is much easier to look through!  Since I was already on the lookout for &lt;code&gt;lognDateFormat&lt;/code&gt;, I took a look at &lt;code&gt;src/lib/locale/formats.js&lt;/code&gt; and saw the following function:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/lib/locale/formats.js
*/
...
export function longDateFormat (key) {
    var format = this._longDateFormat[key],
        formatUpper = this._longDateFormat[key.toUpperCase()];

    if (format || !formatUpper) {
        return format;
    }

    this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
        return val.slice(1);
    });

    return this._longDateFormat[key];
}
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since the TypeError said &lt;code&gt;Cannot read property &#39;toUpperCase&#39; of undefined&lt;/code&gt;, my guess was that &lt;code&gt;longDateFormat&lt;/code&gt; was getting an undefined &lt;code&gt;key&lt;/code&gt; passed to it, which would throw the error when formatUpper is being assigned.  This looked promising! I wrote it down to later investigate who was calling &lt;code&gt;longDateFormat&lt;/code&gt; with what argument as I went through the rest of the code.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I started to really dig in by opening &lt;code&gt;src/moment.js&lt;/code&gt;, the main file for the project.  Taking a look at what it imports, I saw the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/moment.js
*/
...
import {
    defineLocale,
    updateLocale,
    getSetGlobalLocale as locale,
    getLocale          as localeData,
    listLocales        as locales,
    listMonths         as months,
    listMonthsShort    as monthsShort,
    listWeekdays       as weekdays,
    listWeekdaysMin    as weekdaysMin,
    listWeekdaysShort  as weekdaysShort
} from &amp;#39;./lib/locale/locale&amp;#39;;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This let me know that &lt;code&gt;localeData&lt;/code&gt; comes from &lt;code&gt;getLocale&lt;/code&gt;, which lives in &lt;code&gt;src/lib/locale/locale.js&lt;/code&gt;.  Warmer!&lt;/p&gt;
&lt;p&gt;I also noticed that there was another locale directory with the various configurations for different locales.  The first one, &lt;code&gt;af.js&lt;/code&gt;, is the Afrikaans configuration.  The files in this directory were all similarly named, so the &lt;code&gt;localeData&lt;/code&gt; code probably wouldn&amp;rsquo;t live in there.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s part of &lt;code&gt;src/lib/locale/locale.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/lib/locale/locale.js
*/
...
import {
    getSetGlobalLocale,
    defineLocale,
    updateLocale,
    getLocale,
    listLocales
} from &amp;#39;./locales&amp;#39;;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;src/lib/locale/locale.js&lt;/code&gt; didn&amp;rsquo;t have much in it, but &lt;code&gt;getLocale&lt;/code&gt;, which &lt;code&gt;src/moment.js&lt;/code&gt; imports as our friend &lt;code&gt;localeData&lt;/code&gt;, is imported from &lt;code&gt;src/lib/locale/locales.js&lt;/code&gt;.  Note the &amp;ldquo;s&amp;rdquo; on the end!  Definitely made me double check what file I had open a few times.&lt;/p&gt;
&lt;p&gt;Inside &lt;code&gt;src/lib/locale/locales.js&lt;/code&gt;, I found the &lt;code&gt;getLocale&lt;/code&gt; function I had been looking for:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/lib/locale.locales.js
*/
...
// returns locale data
export function getLocale (key) {
    var locale;

    if (key &amp;amp;&amp;amp; key._locale &amp;amp;&amp;amp; key._locale._abbr) {
        key = key._locale._abbr;
    }

    if (!key) {
        return globalLocale;
    }

    if (!isArray(key)) {
        //short-circuit everything else
        locale = loadLocale(key);
        if (locale) {
            return locale;
        }
        key = [key];
    }

    return chooseLocale(key);
}
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At first glance, I didn&amp;rsquo;t see anything particularly helpful in there.  I knew that when I had been calling &lt;code&gt;moment.localeData()&lt;/code&gt;, I hadn&amp;rsquo;t been passing in any argument, so the key for &lt;code&gt;getLocale&lt;/code&gt; would be undefined.  That meant it would return &lt;code&gt;globalLocale&lt;/code&gt; about halfway through the function.  Not super helpful, but let&amp;rsquo;s take a look at &lt;code&gt;globalLocale&lt;/code&gt;.  I did a quick search, and the only reference not in another function was at the top:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/lib/locale.locales.js
*/
...
var globalLocale;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hmm, not very helpful.  At this point, I was thinking that I must be missing something.  &lt;code&gt;moment.localeData()&lt;/code&gt; was clearly not returning undefined, as I was able to call &lt;code&gt;localeData.week()&lt;/code&gt; and others, although not successfully.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;My next step was to take a break from digging and write a failing test to confirm that this error was happening locally.  I took a look in the tests, and I found &lt;code&gt;src/test/moment/locale.js&lt;/code&gt;, which had a few tests for &lt;code&gt;localeData&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/test/moment/locale.js
*/
...
test(&amp;#39;library localeData&amp;#39;, function (assert) {
    moment.locale(&amp;#39;en&amp;#39;);

    var jan = moment([2000, 0]);

    assert.equal(moment.localeData().months(jan), &amp;#39;January&amp;#39;, &amp;#39;no arguments returns global&amp;#39;);
    assert.equal(moment.localeData(&amp;#39;zh-cn&amp;#39;).months(jan), &amp;#39;一月&amp;#39;, &amp;#39;a string returns the locale based on key&amp;#39;);
    assert.equal(moment.localeData(moment().locale(&amp;#39;es&amp;#39;)).months(jan), &amp;#39;enero&amp;#39;, &amp;#39;if you pass in a moment it uses the moment\&amp;#39;s locale&amp;#39;);
});
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since I figured it would throw an error anyways, I wasn&amp;rsquo;t too worried about having a perfect test written.  I added &lt;code&gt;assert.equal(moment.localeData().week(), 1, &#39;Quick week test&#39;)&lt;/code&gt; and ran the tests:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Errors:

Module: locale Test: library localeData
Died on test #1     at global.test.QUnit.test (/path/to/moment/node_modules/qunit/lib/child.js:146:21)
    at /path/to/moment/build/umd/test/moment/locale.js:239:1
    at i (/path/to/moment/build/umd/test/moment/locale.js:4:43)
    at Object.&amp;lt;anonymous&amp;gt; (/path/to/moment/build/umd/test/moment/locale.js:7:2)
    at Module._compile (module.js:556:32)
    at Object.Module._extensions..js (module.js:565:10)
    at Module.load (module.js:473:32)
    at tryModuleLoad (module.js:432:12): Cannot read property &amp;#39;year&amp;#39; of undefined
TypeError: Cannot read property &amp;#39;year&amp;#39; of undefined
    at weekOfYear (/path/to/moment/build/umd/moment.js:1204:41)
    at Locale.localeWeek [as week] (/path/to/moment/build/umd/moment.js:1262:12)
    at Object.&amp;lt;anonymous&amp;gt; (/path/to/moment/build/umd/test/moment/locale.js:244:38)

Global summary:
┌───────┬───────┬────────────┬────────┬────────┬─────────┐
│ Files │ Tests │ Assertions │ Failed │ Passed │ Runtime │
├───────┼───────┼────────────┼────────┼────────┼─────────┤
│ 1     │ 3262  │ 130247     │ 1      │ 130246 │ 25097   │
└───────┴───────┴────────────┴────────┴────────┴─────────┘
Warning: 1 tests failed Use --force to continue.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nice! So this was happening locally too.  I took a look at &lt;code&gt;build/umd/moment.js&lt;/code&gt; at lines 1204 and 1262:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  build/umd/moment.js
*/
...
function weekOfYear(mom, dow, doy) { // Line 1204
    var weekOffset = firstWeekOffset(mom.year(), dow, doy),
        week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
        resWeek, resYear;

    if (week &amp;lt; 1) {
        resYear = mom.year() - 1;
        resWeek = week + weeksInYear(resYear, dow, doy);
    } else if (week &amp;gt; weeksInYear(mom.year(), dow, doy)) {
        resWeek = week - weeksInYear(mom.year(), dow, doy);
        resYear = mom.year() + 1;
    } else {
        resYear = mom.year();
        resWeek = week;
    }

    return {
        week: resWeek,
        year: resYear
    };
}
...
function localeWeek (mom) { // Line 1262
    return weekOfYear(mom, this._week.dow, this._week.doy).week;
}
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Interesting! Not super helpful to view it in the built code, so I &lt;code&gt;grep&lt;/code&gt;ped &lt;code&gt;localeWeek&lt;/code&gt; and found these results (along with others):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;moment: $ grep localeWeek -r src
src/lib/locale/prototype.js:import { localeWeek, localeFirstDayOfYear, localeFirstDayOfWeek } from &amp;#39;../units/week&amp;#39;;
src/lib/locale/prototype.js:proto.week = localeWeek;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Getting warmer!  I opened up &lt;code&gt;src/lib/locale/prototype.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/lib/locale/prototype.js
*/
import { Locale } from &amp;#39;./constructor&amp;#39;;

var proto = Locale.prototype;
...
// Week
import { localeWeek, localeFirstDayOfYear, localeFirstDayOfWeek } from &amp;#39;../units/week&amp;#39;;
proto.week = localeWeek;
proto.firstDayOfYear = localeFirstDayOfYear;
proto.firstDayOfWeek = localeFirstDayOfWeek;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I can see here that &lt;code&gt;prototype&lt;/code&gt; is importing a function named &lt;code&gt;localeWeek&lt;/code&gt; from &lt;code&gt;src/lib/units/week.js&lt;/code&gt; and assigning it to the &lt;code&gt;week&lt;/code&gt; key.  This is where the structure started to make more sense.  &lt;code&gt;prototype.js&lt;/code&gt; defined the methods available to the &lt;code&gt;Locale&lt;/code&gt; constructor, so that when the Locale is created, it has access to everything defined here.  The default locale must create a Locale object that we then can call &lt;code&gt;.week()&lt;/code&gt; from.  That makes more sense!&lt;/p&gt;
&lt;p&gt;The question remains why the &lt;code&gt;.week()&lt;/code&gt; function isn&amp;rsquo;t firing right. I opened up &lt;code&gt;src/lib/units/week.js&lt;/code&gt; and found the &lt;code&gt;localeWeek&lt;/code&gt; function in question:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/lib/units/week.js
*/
...
// LOCALES

export function localeWeek (mom) {
    return weekOfYear(mom, this._week.dow, this._week.doy).week;
}
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alright, we&amp;rsquo;ve seen this one before! So this is the function being given to the prototype, which is accessible to the localeData as &lt;code&gt;.week()&lt;/code&gt;.  The only problem is that &lt;code&gt;localeWeek&lt;/code&gt; is called with a &lt;code&gt;mom&lt;/code&gt; argument, and it passes it to &lt;code&gt;weekOfYear&lt;/code&gt;, which appears in &lt;code&gt;src/lib/units/week-calendar-utils.js&lt;/code&gt; :.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  src/lib/units/week-calendar-utils.js
*/
...
export function weekOfYear(mom, dow, doy) {
    var weekOffset = firstWeekOffset(mom.year(), dow, doy),
        week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
        resWeek, resYear;

    if (week &amp;lt; 1) {
        resYear = mom.year() - 1;
        resWeek = week + weeksInYear(resYear, dow, doy);
    } else if (week &amp;gt; weeksInYear(mom.year(), dow, doy)) {
        resWeek = week - weeksInYear(mom.year(), dow, doy);
        resYear = mom.year() + 1;
    } else {
        resYear = mom.year();
        resWeek = week;
    }

    return {
        week: resWeek,
        year: resYear
    };
}
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So here, we see that the &lt;code&gt;mom&lt;/code&gt; being passed in is used in the first line, where it is accessed with &lt;code&gt;mom.year()&lt;/code&gt;. This means that when we call &lt;code&gt;localeData.week()&lt;/code&gt; without an argument, &lt;code&gt;week&lt;/code&gt;&amp;rsquo;s &lt;code&gt;mom&lt;/code&gt; parameter is undefined, so that when it passes it to &lt;code&gt;weekOfYear&lt;/code&gt;, &lt;code&gt;weekOfYear&lt;/code&gt; calls &lt;code&gt;mom.year()&lt;/code&gt;, which is really &lt;code&gt;(undefined).year()&lt;/code&gt;, which causes the &lt;code&gt;Uncaught TypeError: Cannot read property &#39;year&#39; of undefined&lt;/code&gt;.  This makes sense!  We&amp;rsquo;re missing the argument in &lt;code&gt;week()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It took me longer than it should have to realize that &lt;code&gt;mom&lt;/code&gt; meant a Moment object.  I confirmed this in the jsfiddle:&lt;/p&gt;
&lt;script async src=&#34;//jsfiddle.net/efe9L98p/2/embed/&#34;&gt;&lt;/script&gt;
&lt;p&gt;Clicking on result should successfully alert &lt;code&gt;1&lt;/code&gt;, which makes sense.  It&amp;rsquo;s current January 6 for me, and in the default locale, &lt;code&gt;en&lt;/code&gt;, this is the first week.  This will change accordingly if you&amp;rsquo;re reading this in a later week of the year.&lt;/p&gt;
&lt;p&gt;I investigated &lt;code&gt;longDateFormat()&lt;/code&gt; and &lt;code&gt;relativeTime()&lt;/code&gt; in a similar way (left as an exercise for the reader), and I found that &lt;code&gt;longDateFormat&lt;/code&gt; should have a date format string passed to it (like &lt;code&gt;longDateFormat(String)&lt;/code&gt;), and &lt;code&gt;relativeTime&lt;/code&gt; should be called with a number, a withoutSuffix boolean, a key string, and an isFuture boolean (like &lt;code&gt;relativeTime(Number, Boolean, String, Boolean)&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Armed with this new knowledge, I returned to the GitHub issue and wrote up a response detailing that it was an error in the docs, not an error in the code, and that I would be more than happy to update the docs.  Right before I hit submit, I checked the docs one more time.  To my dismay, I noticed the following information a couple of mouse scrolls below the method signatures:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;localeData.longDateFormat(dateFormat);  // returns the full format of abbreviated date-time formats LT, L, LL and so on
localeData.relativeTime(number, withoutSuffix, key, isFuture);  // returns relative time string, key is on of &amp;#39;s&amp;#39;, &amp;#39;m&amp;#39;, &amp;#39;mm&amp;#39;, &amp;#39;h&amp;#39;, &amp;#39;hh&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;dd&amp;#39;, &amp;#39;M&amp;#39;, &amp;#39;MM&amp;#39;, &amp;#39;y&amp;#39;, &amp;#39;yy&amp;#39;. Single letter when number is 1.
localeData.week(aMoment);  // returns week-of-year of aMoment
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In these three lines we have the info I just found through investigating the code.  This was one of the steps I mised earlier!  I&amp;rsquo;m not sure how I missed this the first time around, but the answer was right there.  This did confirm what I found though, and the fact that it didn&amp;rsquo;t match the above method signatures was a sign that the docs were indeed lacking.  If this new user could make this mistake, followed by me making the mistake, then something could be improved.&lt;/p&gt;
&lt;p&gt;I ended up replying to the issue, opening an issue in the Moment.js docs repository, and submitting a PR with updated method signatures.  Since I knew where to look for these functions, I was able to confirm my understanding of any unclear types in the code.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;postmortem&#34;&gt;Postmortem&lt;/h4&gt;
&lt;p&gt;Digging through the code did give me a better feel for how the library works, and it was great to practice contributing to a project.  While looking through for &lt;code&gt;localeData&lt;/code&gt;, I was able to get a feel for how the library is put together.  I&amp;rsquo;ll also be better prepared for future issues in this library, as I&amp;rsquo;ll already have my basic mental model for the library and can write tests and investigate earlier.&lt;/p&gt;
&lt;p&gt;I did miss the docs, and I also realized that I could have followed the TypeError console traceback after recreating the issue in a jsfiddle.  This would have led me directly to &lt;code&gt;longDateFormat&lt;/code&gt; attempting to call &lt;code&gt;toUpperCase()&lt;/code&gt; on &lt;code&gt;key&lt;/code&gt;.  I ended up getting there anyways, but it did take me a bit longer.  Debugging takes practice, and I&amp;rsquo;ll have to keep these in mind the next time I&amp;rsquo;m digging through an issue.&lt;/p&gt;
&lt;h4 id=&#34;moving-forward&#34;&gt;Moving Forward&lt;/h4&gt;
&lt;p&gt;I didn&amp;rsquo;t end up submitting a PR for the code itself, but I was able to contribute to the documentation and learn more about the library.  Throughout the search, I had that uncomfortable feeling of &amp;ldquo;I don&amp;rsquo;t know what&amp;rsquo;s going on in this library&amp;rdquo;, especially when I switched from investigating &lt;code&gt;localeData&lt;/code&gt; to creating the temporary test.  I know that the uncomfortable feeling is a signal that I&amp;rsquo;m learning, and although it isn&amp;rsquo;t easy, embracing that feeling lets me know that I&amp;rsquo;m pushing my comfort zone in the right direction.&lt;/p&gt;
&lt;p&gt;You can check out the Moment.js code &lt;a href=&#34;https://github.com/moment/moment&#34;&gt;here&lt;/a&gt; and &lt;a href=&#34;https://momentjs.com/&#34;&gt;read through the docs here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;update-april-4-2018&#34;&gt;Update April 4, 2018&lt;/h4&gt;
&lt;p&gt;My PR got merged today, and the docs should be updated with their next release.  Great!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Connecting Arduino with a Node Server</title>
      <link>https://alexanderell.is/posts/arduinode/</link>
      <pubDate>Mon, 01 Jan 2018 09:59:52 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/arduinode/</guid>
      <description>&lt;h3 id=&#34;arduinode&#34;&gt;Arduinode&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve been working through the Arduino Starter Kit projects recently, and I wanted to try a new project that was outside of the pre-made booklet.  Working with an offline Arduino (intranet of things?) has let me learn a great deal, and I wanted to see how difficult it would be to connect my Arduino to a simple Node server, which would get me ready to connect it in any future projects.&lt;/p&gt;
&lt;p&gt;Since I&amp;rsquo;m most comfortable with JavaScript, I decided to create a simple connection between the Arduino and a Node server.  Since a classic &lt;code&gt;Hello, World&lt;/code&gt; Arduino project is to light up an LED, I decided I would make a very basic program that would send a button press from the Arduino to the server, which would then send an action response back to the Arduino to turn an LED on or off.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what the schematic looks like:
&lt;img src=&#34;schematic.png&#34; alt=&#34;Arduino circuitry&#34;&gt;&lt;/p&gt;
&lt;p&gt;After a little research, I came across the &lt;a href=&#34;https://github.com/node-serialport/node-serialport&#34;&gt;serialport &lt;/a&gt;package, which would allow a simple connection between the server and the Arduino.  Easy enough!&lt;/p&gt;
&lt;h4 id=&#34;hello-arduino&#34;&gt;Hello, Arduino&lt;/h4&gt;
&lt;p&gt;My first test was to get an LED shining from a message from the Node server.  I decided to keep it very simple and only send 1 byte at a time, either a &lt;code&gt;1&lt;/code&gt; or a &lt;code&gt;0&lt;/code&gt;.  For now, the server would send a &lt;code&gt;1&lt;/code&gt;, and the Arduino would turn the LED on.  I started with very basic Arduino code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  project.ino
*/
int LEDPin = 6; // LED connected to pin 6
int incomingByte; // for incoming serial data

void setup() {
  // Initialize the LED pin to be an output
  pinMode(LEDPin, OUTPUT);
  // Give the pin low voltage, which turns the LED off
  digitalWrite(LEDPin, LOW);
  // Initialize the Serial connection
  Serial.begin(9600);
}

void loop() {
  // Check if we have a message available
  if (Serial.available() &amp;gt; 0) {
    // read the incoming byte:
    incomingByte = Serial.read();

    // If we have an incoming byte of 1, turn light on.
    if (incomingByte == 1) {
      digitalWrite(LEDPin, HIGH);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not too bad!  My server code was similarly simple.  One thing to note is that there&amp;rsquo;s a slight delay between opening the Serial port and being able to send a message.  Apparently &lt;a href=&#34;https://github.com/noopkat/avrgirl-arduino/issues/43#issuecomment-166146526&#34;&gt;when the port is opened, the Arduino code restarts&lt;/a&gt;, leading to a slight delay. I got around this by adding in a 2 second delay before the server sends the &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  server.js
*/
// Import SerialPort
var SerialPort = require(&amp;#39;serialport&amp;#39;);

// Create a new connection with the correct path to the Arduino and a baudRate that matches
var port = new SerialPort(&amp;#39;/dev/cu.usbmodemFD131&amp;#39;, { baudRate: 9600 }, function(err) {
  if (err) {
    console.log(&amp;#39;Error: &amp;#39;, err.message);
  }
});


// When the connection is open, send the Arduino a 1
port.on(&amp;#39;open&amp;#39;, function() {
  setTimeout(function() {
    port.write([1]);
  }, 2000);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After connecting and uploading the Arduino code, I started the server and the LED turned on!&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&#34;hello-server&#34;&gt;Hello, Server&lt;/h4&gt;
&lt;p&gt;The next step was to get the Arduino talking to the server.  Since I had the server to Arduino code already set, I added an event listener to the server that would listen for new data on the Serial port.  I kept it simple, sending a &lt;code&gt;1&lt;/code&gt; if it heard a &lt;code&gt;1&lt;/code&gt; and a &lt;code&gt;0&lt;/code&gt; if it heard a &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  server.js
*/
var SerialPort = require(&amp;#39;serialport&amp;#39;);

var port = new SerialPort(&amp;#39;/dev/cu.usbmodemFD131&amp;#39;, { baudRate: 9600 }, function(err) {
  if (err) {
    console.log(&amp;#39;Error: &amp;#39;, err.message);
  }
});


port.on(&amp;#39;open&amp;#39;, function() {
  // Add event listener for data
  port.on(&amp;#39;data&amp;#39;, function(data) {
    // Log the incoming data so we can see it
    console.log(data[0]);
    // If we get a 1, send a 1 back
    if (data[0]) {
      port.write([1]);
    } else {
      // Otherwise, send a 0
      port.write([0]);
    }
  });
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, I needed to make the Arduino listen for a button press change.  I kept track of the state of the switch, whether it&amp;rsquo;s open or closed, and the previous state.  This allowed me to only send a message to the server when something new happened, instead of constantly sending messages.  Essentially, pressing the switch down would send one message to the server, instead of many messages while the switch is open and many messages once the button was pressed down. Given how often the Arduino runs its loop, I wanted to keep the flow simple.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  project.ino
*/
int LEDPin = 6;
int incomingByte; // for incoming serial data

// Initialize variables to show that button starts not pressed
int switchState = 0;
int previousSwitchState = 0;

void setup() {
  pinMode(LEDPin, OUTPUT);
  // Initialize the button pin to input
  pinMode(2, INPUT);
  digitalWrite(LEDPin, LOW);

  Serial.begin(9600);
}

void loop() {
  // Read the voltage coming in from the button
  switchState = digitalRead(2);

  if (switchState == HIGH &amp;amp;&amp;amp; switchState != previousSwitchState) {
    // If the switchState is pressed and it didn&amp;#39;t used to be, send message to server
    Serial.write(1);
    previousSwitchState = switchState;
  } else if (switchState == LOW &amp;amp;&amp;amp; switchState != previousSwitchState) {
    Serial.write(0);
    previousSwitchState = switchState;
  }

  // Check if we have a message available
  if (Serial.available() &amp;gt; 0) {
    // read the incoming byte:
    incomingByte = Serial.read();

    if (incomingByte == 1) {
      digitalWrite(6, HIGH);
    } else {
      digitalWrite(6, LOW);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, inside the main loop, the Arduino will check if the button is pressed and if it&amp;rsquo;s different than the last time it checked.  If it is different, it will send either a 1 or a 0 to the server.  The server, seeing that 1 or 0, will respond appropriately, and the Arduino will then turn the LED on or off.&lt;/p&gt;
&lt;p&gt;This worked!  I then realized that it could be working because the Arduino is reading the byte that it just sent, which would mean that it was really bouncing these messages off of a mirror to turn the light on.  As a sanity check, I stopped the node server, at which point the light stopped turning on or off.  This left me confident that the server was in fact playing its role correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;All that was left was some refactoring to clean up the code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  server.js
*/
/*
  This is the very simple server code for listening to and responding to the arduino&amp;#39;s button press.
  It listens for data from the arduino, and if it receives an ON signal, it returns an ON signal. Likewise for OFF.
*/
var SerialPort = require(&amp;#39;serialport&amp;#39;);
var PATH = &amp;#39;/dev/cu.usbmodemFD131&amp;#39;;

var port = new SerialPort(PATH, { baudRate: 9600 }, function(err) {
  if (err) {
    console.log(&amp;#39;Error: &amp;#39;, err.message);
  }
});

port.on(&amp;#39;open&amp;#39;, function() {
  port.on(&amp;#39;data&amp;#39;, function(data) {
    console.log(data[0]);
    if (data[0]) {
      port.write([1]);
    } else {
      port.write([0]);
    }
  });
});
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/*
  project.ino
*/
/*
Setup:
  Initialize I/O pins
  Initialize Serial port
Loop:
  Check for button press change and send to server with Serial
  Check for incoming message and turn LED on/off accordingly
*/

int LEDPin = 6;
int incomingByte; // for incoming serial data
int switchPin = 2;
int switchState = LOW;
int previousSwitchState = LOW;

void setup() {
  pinMode(LEDPin, OUTPUT);
  pinMode(switchPin, INPUT);

  // Start with LED turned off
  digitalWrite(LEDPin, LOW);

  Serial.begin(9600);
}

void loop() {
  // Read button press
  switchState = digitalRead(switchPin);

  // Check previousSwitchState so we aren&amp;#39;t constantly writing back and forth without a button change
  if (switchState != previousSwitchState) {
    Serial.write(switchState);
    previousSwitchState = switchState;
  }

  // Check if we have a message available
  if (Serial.available() &amp;gt; 0) {
    // read the incoming byte:
    incomingByte = Serial.read();
    digitalWrite(LEDPin, incomingByte);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;next-steps&#34;&gt;Next steps&lt;/h4&gt;
&lt;p&gt;This project served as a good way to test out the communication between Arduino and computer, and it was an easy way to get something up and running with &lt;code&gt;serialport&lt;/code&gt;.  With this project done, it&amp;rsquo;ll be very easy to integrate the Arduino into any future Node project.&lt;/p&gt;
&lt;p&gt;You can see the full code on my GitHub page &lt;a href=&#34;https://github.com/AlexanderEllis/arduino/tree/master/simple-arduino-node&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MBTA Bus Mirrors</title>
      <link>https://alexanderell.is/posts/mbta-bus/</link>
      <pubDate>Wed, 06 Dec 2017 08:51:05 -0500</pubDate>
      
      <guid>https://alexanderell.is/posts/mbta-bus/</guid>
      <description>&lt;p&gt;I take the MBTA 86 bus as part of my commute in the morning. It runs from Reservoir in Brighton to Sullivan Station in Cambridge, and it stops at Harvard on the way, which is where I get off.&lt;/p&gt;
&lt;p&gt;Right after the Eliot street stop, there are two ways for the bus to get underground to the Harvard stop. One way is to go down Bennett St., take a right on University Road, take a right on Mt. Auburn, and then take a left into the bus tunnel.  The other way is to go through Bennett Alley and continue straight into the bus tunnel. This way is shorter, and it’s the preferred route of every driver I’ve ridden with.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;routes.png&#34; alt=&#34;Routes&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bennett Alley is for MBTA traffic only, and the the bus driver usually decides on the route to take based on how many buses are already in Bennett Alley. Often times, there will be one bus parked to the left and one parked to the right, and the bus driver is able to drive through the middle.  If there are already three buses in the alley or one of the two is parked at an angle reducing the space left for our bus, the driver will instead take the longer route around.&lt;/p&gt;
&lt;p&gt;If I had to guess how wide Bennett Alley is, I would say it’s just larger than 3 busses wide. When our bus goes between two others, there’s often inches to spare. One of the reasons our bus is able to make it through is because of the mirrors.&lt;/p&gt;
&lt;p&gt;The left and right mirrors on every MBTA bus are vertically offset to allow two parallel buses to overlap their mirror space without hitting each other.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;mirrors.png&#34; alt=&#34;Bus Mirrors&#34;&gt;&lt;/p&gt;
&lt;p&gt;If the two buses had mirrors at the same height, and additional ~8 inches would be required on either side. This would increase the width required for three buses by ~16 inches.  This doesn’t sound like much, but even a foot is often the difference between being able to drive through Bennett Alley and taking the slightly longer way around.&lt;/p&gt;
&lt;p&gt;It’s a small design decision that saves just a bit of space.  It’s not helpful in the large majority of situations, but given Boston’s often tight streets, it gives just a little more breathing room for the drivers.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Temperature Sensing with Arduino</title>
      <link>https://alexanderell.is/posts/arduino-project-3/</link>
      <pubDate>Sun, 13 Aug 2017 15:16:13 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/arduino-project-3/</guid>
      <description>&lt;p&gt;I worked through the third basic project in the Arduino Starter Kit Projects Book.  I like to write about the projects to reinforce my own understanding.&lt;/p&gt;
&lt;p&gt;The third project, the &amp;ldquo;Love-O-Meter&amp;rdquo;, creates a simple circuit with a temperature sensor.  Using the Analog-to-Digital converter, the Arduino reads in the value coming from the temperature sensor, then lights up a combination of 3 LEDs depending on that value.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what my circuit looked like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;circuit.JPG&#34; alt=&#34;Circuit Diagram&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can see the temperature sensor in the top right connected to the positive, the analog A0 pin, and the ground.  3 LEDs are each connected to one of the digital ports on the right side of the arduino.&lt;/p&gt;
&lt;p&gt;To begin, two constant variables were created for the sensor pin and the baseline temperature.  In the setup function, output pins 2, 3, and 4 were put in output mode and were sent low voltage (keeping the LEDs off).  The serial port was also initialized to send logging information back to the computer.  9600 refers to sending the computer 9600 bits per second.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const int sensorPin = A0;
const float baselineTemp = 25.0;

void setup() {
  Serial.begin(9600);
  for (int pinNumber = 2; pinNumber &amp;lt; 5; pinNumber++) {
    pinMode(pinNumber, OUTPUT);
    digitalWrite(pinNumber, LOW);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The middle wire of the temperature sensor was connected to the A0 analog in-pin.  Inside the main loop, the Arduino receives receives a value between 0-1023 from the sensor through this pin.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const int sensorPin = A0;
int sensorVal = analogRead(sensorPin);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because the total voltage available to the sensor is 5 volts, the value between 0-1023 can be mapped to a voltage between 0 and 5 volts.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;float voltage = (sensorVal / 1024.0) * 5.0;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The voltage output can then be used to convert to temperature.  The data sheet for the &lt;a href=&#34;http://www.analog.com/media/en/technical-documentation/data-sheets/TMP35_36_37.pdf&#34;&gt;TMP36 temperature sensor&lt;/a&gt; has information about the conversion, and the relevant table is below:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;TMP36-output-characteristics.png&#34; alt=&#34;TMP36 Output Characteristics&#34;&gt;&lt;/p&gt;
&lt;p&gt;This means that there is a 0.5 V offset for the readings, and 10 mV (or 0.01 V) corresponds to a 1°C change.  We can confirm this with the output voltage at 25°C.  With an output of 750 mV, we can subtract the offset voltage (0.5 V, or 500 mV), which then gives us 250 mV.  Dividing by 10 mV/°C gives us the correct value of 25°C.&lt;/p&gt;
&lt;p&gt;We could also do this conversion in volts (instead of millivolts) by subtracting 0.5 V and dividing by 0.01 V/°C, which is the same as multiplying by 100.  In other words:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;float temperature = (voltage - 0.5) * 100;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can send these variables to the computer for logging using Serial.print:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Serial.print(&amp;#34;Sensor Value: &amp;#34;);
Serial.print(sensorVal);
Serial.print(&amp;#34;, Volts: &amp;#34;);
Serial.print(voltage);
Serial.print(&amp;#34;, degrees C: &amp;#34;);
Serial.println(temperature);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The function can then turn on a combination of the LEDs based on the difference between the temperature reading and the baseline temperature.  By assigning the output voltage to be HIGH, the Arduino sends the high value of voltage (5 V) out of the digital output pin, which then flows through and shines the corresponding LED.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;  if (temperature &amp;lt; baselineTemp) {
    digitalWrite(2, LOW);
    digitalWrite(3, LOW);
    digitalWrite(4, LOW);
  } else if (temperature &amp;gt;= baselineTemp &amp;amp;&amp;amp; temperature &amp;lt; baselineTemp + 2) {
    digitalWrite(2, HIGH);
    digitalWrite(3, LOW);
    digitalWrite(4, LOW);
  } else if (temperature &amp;gt;= baselineTemp + 2 &amp;amp;&amp;amp; temperature &amp;lt; baselineTemp + 4) {
    digitalWrite(2, HIGH);
    digitalWrite(3, HIGH);
    digitalWrite(4, LOW);
  } else if (temperature &amp;gt;= baselineTemp + 4) {
    digitalWrite(2, HIGH);
    digitalWrite(3, HIGH);
    digitalWrite(4, HIGH);
  }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At the end of the loop, a very slight delay is included to create a pause in between the analog-to-digital readings to ensure that the Analog-to-Digital Converter (ADC) &lt;a href=&#34;https://www.quora.com/Why-is-a-little-delay-needed-after-analogRead-in-Arduino&#34;&gt;can reset successfully before the next reading&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;  delay(10);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, the result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;demo.gif&#34; alt=&#34;Result gif&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can see the full code &lt;a href=&#34;https://gist.github.com/AlexanderEllis/5d25657a4d04ce2848e1747feaf52bfe&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Noticing UI: Shazam&#39;s loading screen</title>
      <link>https://alexanderell.is/posts/noticing-ui-shazam/</link>
      <pubDate>Sat, 05 Aug 2017 21:29:38 -0400</pubDate>
      
      <guid>https://alexanderell.is/posts/noticing-ui-shazam/</guid>
      <description>&lt;h3 id=&#34;shazams-loading-screen&#34;&gt;Shazam&amp;rsquo;s loading screen&lt;/h3&gt;
&lt;p&gt;I was pleasantly surprised by Shazam&amp;rsquo;s iPhone app loading screen, and I took a look into what makes it stand out.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what it looked like:&lt;/p&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&#34;shazam-loading.webm&#34;  type=&#34;video/webm&#34;&gt;
  &lt;source src=&#34;shazam-loading.mp4&#34;  type=&#34;video/mp4&#34;&gt;
&lt;/video&gt;

&lt;p&gt;This felt different, and it made me look at the experience of opening apps.  I started by spending a bus ride home from work opening and closing apps to get a feel for why this wasn&amp;rsquo;t like other apps.&lt;/p&gt;
&lt;h4 id=&#34;loading&#34;&gt;Loading&amp;hellip;&lt;/h4&gt;
&lt;p&gt;A loading screen tells a user that the app or other result is not ready yet.  This is good.  The user can&amp;rsquo;t try to interact with anything before it&amp;rsquo;s ready.  There are no non-working buttons or menus to click and get frustrated with.  It means that there is a pause in between asking for the app and being able to use it, which means a delay in the user&amp;rsquo;s experience.&lt;/p&gt;
&lt;p&gt;Companies often use this in-between view to show the company&amp;rsquo;s logo (just in case the user forgot what app they opened).  Some companies use this as a chance to help the user connect the company to their logo (just in case the logo has no words or markings about which company it is).  I saw this for Uber, Audible, and YNAB.&lt;/p&gt;
&lt;p&gt;You can open five different apps from your phone/tablet and see how they chose to handle the in between time.  My experience was that most of them were a simple separate view featuring the company&amp;rsquo;s logo and name.&lt;/p&gt;
&lt;h4 id=&#34;a-would-open-again&#34;&gt;&amp;ldquo;A++ WOULD OPEN AGAIN&amp;rdquo;&lt;/h4&gt;
&lt;p&gt;One thing that I enjoyed about Shazam&amp;rsquo;s take is that the overall structure between the loading page and the ready app is the same.  There are no jumps to different views or new screens.  Instead, the app feels like it comes alive under your thumb.  The faded S logo lights up with the animated S and transforms into a button.   The shifted gradient makes the circle pop more &lt;a href=&#34;https://en.wikipedia.org/wiki/Optical_illusion#Color_and_brightness_constancies&#34;&gt;like the optical illusion of background color changing an object&amp;rsquo;s color&lt;/a&gt;, and the changing circle size gives further depth.&lt;/p&gt;
&lt;p&gt;Part of the reason this works best for Shazam is that pressing the button has been designed as the main functionality of the app.  The button allows Shazam to listen to and find a match for your audio, and pressing that button is likely the user&amp;rsquo;s goal when opening the app.  Not only has the app loaded when the button comes alive, but you&amp;rsquo;re now able to go directly to what you&amp;rsquo;re looking for.  The My Shazam and Discover sections are secondary and accessible after the button has been presented.  I like this.  The extra clicks add up.&lt;/p&gt;
&lt;p&gt;This doesn&amp;rsquo;t deviate from normal app behavior.  It&amp;rsquo;s still showing the logo while loading, and it&amp;rsquo;s still reinforcing the connection between the company and the slanted S (or is that a slanted &amp;ldquo;u&amp;rdquo; and an &amp;ldquo;n&amp;rdquo; on top of each other? No, I opened Shazam, probably an S).  The additional transition to the button, an extension of the logo, allows the user to pass easily from the usual loading view to the ready app.&lt;/p&gt;
&lt;p&gt;This connection would not be as clear for a different app.  For a notetaking app, the importance of reviewing notes may match starting a new note.  If you tried to connect the app&amp;rsquo;s logo loading screen with one button, which would you pick?  For something like Facebook, how would you connect the loading screen to the timeline?&lt;/p&gt;
&lt;p&gt;One similar example that I noticed was Instagram&amp;rsquo;s app, which loads with the header (with logo), a blank viewport for the content, and the footer area (without menu options).  This also builds continuity between the structure of the loading screen and the structure of the ready app.&lt;/p&gt;
&lt;h4 id=&#34;keep-it-simple-shazam&#34;&gt;Keep It Simple Shazam&lt;/h4&gt;
&lt;p&gt;Shazam is able to build a simple loading user experience with a smooth animation that connects their logo to the app&amp;rsquo;s purpose.  It&amp;rsquo;s not for every case, but for the right case, it&amp;rsquo;s a good move.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
