maxdeviant.comZola2024-01-10T05:10:51.527+00:00https://maxdeviant.com/atom.xmlAI Continues to Impress2024-01-10T05:10:51.527+00:002024-01-10T05:10:51.527+00:00Unknownhttps://maxdeviant.com/shards/2024/ai-continues-to-impress/<p>I make no secret of being a bit of an AI skeptic, however, I will give credit where it's due.</p>
<p>Today I've had two AI experiences that left me feeling impressed. Both of these involved leveraging the AI assistant built into <a href="https://zed.dev/">Zed</a>.</p>
<p>Earlier today I was writing some documentation in the Zed codebase. I had two structs with the same fields: an internal settings struct and a separate struct for serialization. One of these structs had its fields documented while the other did not.</p>
<p>While I could have copied the doc comments over by hand, I decided to enter this prompt into the inline assistant to see what would happen:</p>
<blockquote>
<p>Copy the doc comments from the fields in <code>LanguageSettingsContent</code> and add them to the fields in <code>LanguageSettings</code>. Ignore the note about the default value. Just bring over the doc comments.</p>
</blockquote>
<p>The AI copied over the doc comments verbatim—sans the note about the default value that I asked it to ignore—and put them on the corresponding fields.</p>
<hr />
<p>Later this evening I was doing some work on my site, translating some of my templates from Tera over to a new templating system I'm building on top of <a href="https://docs.rs/auk/latest/auk/"><code>auk</code></a>.</p>
<p>I needed to port this small component I had written over to Auk's DSL:</p>
<pre data-lang="html" style="background-color:#212121;color:#eeffff;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#89ddff;"><</span><span style="color:#f07178;">div </span><span style="font-style:italic;color:#ffcb6b;">class</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">pv2 flex items-end</span><span style="color:#89ddff;">">
</span><span> </span><span style="color:#89ddff;"><</span><span style="color:#f07178;">div </span><span style="font-style:italic;color:#ffcb6b;">class</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">pv1 w-20</span><span style="color:#89ddff;">"></span><span>{{ label }}</span><span style="color:#89ddff;"></</span><span style="color:#f07178;">div</span><span style="color:#89ddff;">>
</span><span> </span><span style="color:#89ddff;"><</span><span style="color:#f07178;">div </span><span style="font-style:italic;color:#ffcb6b;">class</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">pv1 w-80</span><span style="color:#89ddff;">">
</span><span> </span><span style="color:#89ddff;"><</span><span style="color:#f07178;">progress </span><span style="font-style:italic;color:#ffcb6b;">class</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">flex language-bar</span><span style="color:#89ddff;">" </span><span style="font-style:italic;color:#ffcb6b;">value</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">{{ value }}</span><span style="color:#89ddff;">" </span><span style="font-style:italic;color:#ffcb6b;">max</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">10</span><span style="color:#89ddff;">"></</span><span style="color:#f07178;">progress</span><span style="color:#89ddff;">>
</span><span> </span><span style="color:#89ddff;"></</span><span style="color:#f07178;">div</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"></</span><span style="color:#f07178;">div</span><span style="color:#89ddff;">>
</span></code></pre>
<p>Once again, I thought it would be interesting to see what the Zed assistant could do with this. I prompted it with:</p>
<blockquote>
<p>Rewrite this HTML using <code>auk</code>'s DSL like the <code>post_link</code> function above.</p>
</blockquote>
<p>The end result that the assistant spit out looked identical to what I would have produced by hand:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">language_stat</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">label</span><span style="color:#89ddff;">: &</span><span style="font-style:italic;color:#c792ea;">str</span><span>, </span><span style="color:#f78c6c;">value</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">u32</span><span style="color:#89ddff;">) -></span><span> HtmlElement </span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#82aaff;">div</span><span style="color:#89ddff;">()
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">class</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">pv2 flex items-end</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">child</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">div</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">class</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">pv1 w-20</span><span style="color:#89ddff;">").</span><span style="color:#82aaff;">text_content</span><span style="color:#89ddff;">(</span><span>label</span><span style="color:#89ddff;">))
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">child</span><span style="color:#89ddff;">(
</span><span> </span><span style="color:#82aaff;">div</span><span style="color:#89ddff;">().</span><span style="color:#82aaff;">class</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">pv1 w-80</span><span style="color:#89ddff;">").</span><span style="color:#82aaff;">child</span><span style="color:#89ddff;">(
</span><span> </span><span style="color:#82aaff;">progress</span><span style="color:#89ddff;">()
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">class</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">flex language-bar</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">value</span><span style="color:#89ddff;">(</span><span>value</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">to_string</span><span style="color:#89ddff;">())
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">max</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">10</span><span style="color:#89ddff;">"),
</span><span> </span><span style="color:#89ddff;">),
</span><span> </span><span style="color:#89ddff;">)
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>I still don't buy into the notion that AI will make software developers obsolete. Rather, I think that a judicious integration of AI into our development tools and workflows can make us all more effective at building software.</p>
Goals for 20242024-01-08T00:04:19.973+00:002024-01-08T00:04:19.973+00:00Unknownhttps://maxdeviant.com/shards/2024/goals/<p>In the past I've typically bundled up my goals or resolutions for the new year along with my year-in-review post.</p>
<p>This year I wanted to try separating them. While this was partly due to my year-in-review coming together at the last minute, it also occurred to me that reviewing and planning are rather distinct operations.</p>
<p>Reviewing requires looking at the past, but planning looks the opposite way: towards the future. Trying to do both at once ends up splitting your vision.</p>
<h2 id="goals">Goals</h2>
<h3 id="devise-a-system-for-sustaining-my-ideal-lifestyle">Devise a system for sustaining my ideal lifestyle</h3>
<p>Right off the bat I'll say I'm not entirely happy with this goal. It's large, ambiguous, and overall ill-defined.</p>
<p>However, I think it best captures the essence of what I want to achieve: <strong>finding a way to sustain good habits over the long term</strong>.</p>
<p>I have a tendency to hyperfocus, which can turn even a beneficial or healthy habit into a detriment due to it being prioritized to point of neglecting everything else.</p>
<p>As the year progresses, I anticipate I will break this down into smaller, more actionable goals that can bring me closer to the desired outcome.</p>
<h3 id="keep-my-desk-clean">Keep my desk clean</h3>
<p>My desk—much like any other system—trends towards disorder when left unattended.</p>
<p>By virtue of working remotely and having hobbies that primarily revolve around the computer, I spend a large portion of my time at my desk.</p>
<p>Ergo, I am subjected to the state of my desk for long periods of time, and this has the potential to bolster or negatively impact my mood.</p>
<h3 id="redesign-my-website">Redesign my website</h3>
<p>As I continue to become more and more disenchanted with the state of social media and platforms that are increasingly becoming more walled off, it is becoming clear that investing into my website is the best thing I can do.</p>
<p>To that end, I want to redesign my website to provide a better experience, both for myself as the author and for those who visit it.</p>
2023 in Review2024-01-01T04:59:30.258+00:002024-01-01T04:59:30.258+00:00Unknownhttps://maxdeviant.com/posts/2023/in-review/<p>I haven't written a year-in-review since <a href="https://maxdeviant.com/posts/2019/in-review/">2019</a>. I'm sure it's no coincidence that this was the last one before COVID-19 reared its ugly head and threw things into disarray.</p>
<p>I find that it can be hard to write these when everything since March 2020 bleeds together for me, but perhaps that is all the more reason that I should be writing them.</p>
<p>So in hopes of a return to form, here is my 2023 year-in-review.</p>
<h2 id="workos-zed">WorkOS → Zed</h2>
<p>After just shy of three years at WorkOS, in September I left WorkOS and started working at Zed Industries.</p>
<p>I've been assisting with a massive rewrite since I started, which we'll be launching early next year. I'm very much looking forward to getting that out into the world and getting to work on our 2024 roadmap!</p>
<h2 id="grief-and-therapy">Grief and Therapy</h2>
<p>This year marked the 13th anniversary of my family moving back to the US from China. This was a massive transition for me, and I'm still uncovering the ways in which it's impacted me since.</p>
<p>In the past two years I've started to explore and unpack these feelings.</p>
<p>Back in June 2022 I first learned the term "unresolved grief" and was able to put a name to the weight I've been shouldering all this time. I've also learned that it's a fairly common phenomenon among third culture kids (TCKs), like myself.</p>
<p>After dragging my feet for a while, I finally started seeing a therapist this year. She is a TCK herself and has experience counseling TCKs through their unresolved grief.</p>
<p>So far attending therapy has been immensely helpful as I start to unpack all of the baggage from my life overseas.</p>
<p>Two things that I've learned in the process thus far:</p>
<ol>
<li>There is way more to unpack than I initially thought. I feel like I'm just at the tip of the iceberg.</li>
<li>I need to give myself the space to process my experiences, and understand that it's not a straight path to being fully healed.</li>
</ol>
<h2 id="projects">Projects</h2>
<h3 id="crane">Crane</h3>
<p>In June I started work on something that I've wanted to build for a long time: my own programming language.</p>
<p>This undertaking has always been on the shelf as a "someday" project, with the skills and expertise needed to pull this off always feeling out of reach.</p>
<p>One day I was struck by the realization that not building a programming language due to feeling ill-versed on how to build one was a self-fulfilling prophecy of failure. That was the kick I needed to take the plunge and do <em>something</em> to move me closer to the goal.</p>
<p>And thus began <a href="https://crane-lang.org/">Crane</a>.</p>
<p>There's still a long ways to go before it feels like an honest-to-God programming language, but I'm proud of taking the first step in the right direction.</p>
<h3 id="untitled-morpg">Untitled MORPG</h3>
<p>In a somewhat similar vein, in August I resumed work on a long-running project of building an MMO-lite.</p>
<p>MMORPGs have always been my favorite games to play, and I've always wanted to make one that was my own. Unfortunately, MMORPGs are also the most difficult games to make, and making one solo is almost certainly impossible.</p>
<p>However, I still believe it possible—with sufficient scope-adjusting—to make a game that bears some resemblance to an MMORPG that I would enjoy playing.</p>
<p>I've worked on this project off-and-on for a while now, but this year I made some significant progress towards something that is actually starting to feel like a game.</p>
<p>I'm still not ready to share too much about it publicly, but what I can say is that I have the bones in place for an RPG where multiple players can interact within a shared, persistent world controlled by a separate server.</p>
<h2 id="goal-review">Goal Review</h2>
<p>At the beginning of the year I put together a <a href="https://maxdeviant.com/shards/2023/hello-2023/">short list of goals</a> for this year.</p>
<h3 id="successes">Successes</h3>
<blockquote>
<p>Write Rust, and a lot of it</p>
</blockquote>
<p>I've been shouting Rust's praises from the rooftops for a while now, all the while not having a ton of experience using it in anger. I wanted to change that this year, and set myself this goal of writing lots of Rust code.</p>
<p>At the time, I was still using TypeScript at my day job, which meant that any Rust I was writing was still in my spare time.</p>
<p>The bulk of my Rust coding was in the two projects discussed above: the Crane compiler and my work-in-progress multiplayer game.</p>
<p>Since joining Zed Industries in September, I'm now writing Rust full-time, which has been a dream come true.</p>
<p>With all the Rust I've written this year, I can safely say that this goal was an absolute success.</p>
<blockquote>
<p>Distance myself from platforms I don't control</p>
</blockquote>
<p>I wrote this goal in the wake of Twitter being taken over and summarily <a href="https://maxdeviant.com/newsletters/errata-exist/0018-the-sack-of-twitter-2022/">sacked</a>.</p>
<p>These events left me feeling wary of all platforms that I don't have direct control over, and prompted a shift towards owning as much of my data as possible.</p>
<p>For instance, I now have a routine job that syncs all of my Last.fm listening data over to flat files in a GitHub repo<sup class="footnote-reference"><a href="#1">1</a></sup>. I have all my tweets backed up there as well, up until Twitter did away with API access.</p>
<p>I'm still spending far too much time being consumptive on platforms like Twitter and Instagram.</p>
<blockquote>
<p>Connect with others more intentionally</p>
</blockquote>
<p>For a while now I've been feeling increasingly isolated in my personal life. I've fallen out of touch with most of my old friends due to time, distance, or both.</p>
<p>While I didn't make as much progress as I would have liked in this area, I did try to be more intentional about connecting with my parents and siblings.</p>
<p>Still a long ways to go.</p>
<h3 id="failures">Failures</h3>
<blockquote>
<p>Do better at planning and cooking meals</p>
</blockquote>
<p>It doesn't feel like there's been any noticeable improvement in this area.</p>
<p>Getting dinner on the table each night still feels like a chore, and I usually find myself scrambling at the last minute to figure out what we're going to eat. This often results in eating less-than-healthy meals, like frozen pizza.</p>
<blockquote>
<p>Find a form of exercise that I don't hate</p>
</blockquote>
<p>I really haven't been good about exercising this year. I've tried riding the stationary bike a bit, but it never lasts more than a week or two before I fall out of the habit.</p>
<p>On a positive note, I upgraded my home office to an adjustable standing desk this year. I try to stand for about half of my work day, which I've found has left me feeling better overall.</p>
<h2 id="stats">Stats</h2>
<h3 id="code">Code</h3>
<p>I'm quite happy with my code stats for this year.</p>
<p>My GitHub chart has a lot of green:</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01hk1mdpsrkez3bd9rntzgw1qx" alt="My GitHub contribution chart showing 6,069 commits." /></p>
<p>I also hit the ground running in the Zed repo:</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01hjrvh50ar70vvp4gx8d38vbw" alt="My contribution graph for the Zed repo. 562 commits. 93,577 lines added and 80,047 lines removed." /></p>
<h3 id="music">Music</h3>
<p>Overall my music listening was down a bit from previous years, but I still found time to listen to a bunch of it.</p>
<p>These are the albums I listened to the most in 2023:</p>
<ol>
<li><a href="https://open.spotify.com/album/7CLRg9B3ArYKG40QjzEXhS?si=gTA7PRWYR0aKuBNuGP2D6w"><em>Equinox</em></a> - City State</li>
<li><a href="https://open.spotify.com/album/7EQ6sixCqVYPCPqARdU1Ct?si=kyVwNuCoSQKjJf7aox568A"><em>Nosebleeds</em></a> - MisterWives</li>
<li><a href="https://open.spotify.com/album/3J353DwtMfUV3V6t9vRPCk?si=P7iltFQ8TLKT8h6BZs4DnA"><em>Zig</em></a> - Poppy</li>
<li><a href="https://open.spotify.com/album/0GIruvY3PiwKFylUzETbLl?si=51Zc7N5ISr6TTnAwnLow0Q"><em>The Fortress in the Forest</em></a> - Sanguine Forest</li>
<li><a href="https://open.spotify.com/album/4CLLPZzm1QDzUZ8FTkNPyy?si=VhDtVN3yTM2bLLq_RBum_w"><em>EVERGREEN</em></a> - PVRIS</li>
<li><a href="https://open.spotify.com/album/3WxKplxyqg68yCQHtNucAo?si=SSnF3gaKRs-zD_uvlo_mlg"><em>Всё</em></a> - Рожь</li>
<li><a href="https://open.spotify.com/album/0yRDXJyXgyTbBqqONVcX27?si=w5XSkxMoQCqw_V3F_Jk9pA"><em>DAMSEL IN DISTRESS</em></a> - GIRLI</li>
<li><a href="https://open.spotify.com/album/5CEfuHULnZDi68wT8mABle?si=0q9lJzveR66yVYZTKqctkQ"><em>Blackbraid II</em></a> - Blackbraid</li>
<li><a href="https://open.spotify.com/album/5hOFWwfkeWetTEnMTbX4q9?si=Q6cEZInrSaO3PmWOomdVgA"><em>Gag Order</em></a> - Kesha</li>
<li><a href="https://open.spotify.com/album/1TT5TmHzJtV9ESmRtSdl8L?si=Bz95hgKATDq_Lq1sYQ2tQQ"><em>In The Faith That Looks Through Death</em></a> - Vital Spirit</li>
<li><a href="https://open.spotify.com/album/2OesOgMYCSCOt1Y7rPLXeP?si=iLH1A13aS-CW_miM-asXvA"><em>Digital Pacific</em></a> - Luna Shadows</li>
<li><a href="https://open.spotify.com/album/4x7nLB8awvm37yZBOyQTzL?si=MBuGh91hQImZU4dnEYZ-QA"><em>Volume II</em></a> - Manuel Gardner Fernandes</li>
<li><a href="https://open.spotify.com/album/1wIuFndiINuEZLFg3OIWGf?si=hiq-TCKyTs-YslGHAigBXA"><em>VOL. 4 :: SLAVES OF FEAR</em></a> - HEALTH</li>
<li><a href="https://open.spotify.com/album/7krgzxFJr9YxsmyWlO5Ubg?si=1F1B7iSlRj6es6uv3BffnA"><em>Kx5</em></a> - kx5</li>
<li><a href="https://open.spotify.com/album/4yP0hdKOZPNshxUOjY0cZj?si=rZEdgx7yTbi-1Fx9b4Rwvw"><em>After Hours</em></a> - The Weeknd</li>
<li><a href="https://open.spotify.com/album/4J2xyrxoURH3t6plYCEPhf?si=ONzp9rsAQdikTjqy_EzTpA"><em>Euphoric</em></a> - Georgia</li>
<li><a href="https://open.spotify.com/album/2jgr5I8IJT1brlDktQEt3z?si=T8uIR2MgSV2aVzaXkmnPNQ"><em>HELLO, THANK YOU</em></a> - Blvck Ceiling</li>
<li><a href="https://open.spotify.com/album/13vlDeD4CxuoUqL4Ir3ojZ?si=-riS98U2THGD3mTZWAA2ag"><em>Diorama</em></a> - MØL</li>
<li><a href="https://open.spotify.com/album/2EzMAlUUwE0GRsqzz8Akfr?si=-aVicqceSUicz55WECNG9w"><em>Dystopia</em></a> - Old Seas Young Mountains</li>
<li><a href="https://open.spotify.com/album/6yMmUKkAfVoHJT71ZpoBi5?si=vH4WK4CGRFm2AH7WYQHBTg"><em>Fatalism</em></a> - Polaris</li>
</ol>
<h2 id="fin">(*Fin)</h2>
<p>For me, 2023 has embodied the polar extremes of good and bad.</p>
<p>This year has been simultaneously tumultuous and rewarding, joyous and mournful. I feel like I've learned more about myself in this year alone than in all of the years preceeding it. But these discoveries have often come at the cost of confronting what has been buried deep in my psyche.</p>
<p>And yet, I remain optimistic that the best days are ahead, and that this new year will contain much to be thankful for.</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>Still figuring out contingency plans for GitHub itself...</p>
</div>
Joining Zed2023-12-13T15:04:25.190+00:002023-12-13T15:04:25.190+00:00Unknownhttps://maxdeviant.com/posts/2023/joining-zed/<p>Today is my first (official) day at <a href="https://zed.dev/">Zed Industries</a>.</p>
<p>At Zed Industries we're building <strong>Zed</strong>: a high-performance and collaborative code editor for you and your team.</p>
<p>For the past twelve weeks I've been consulting at Zed Industries, assisting with the Herculean effort of rewriting Zed on top of GPUI2, the new and improved version of our UI framework.</p>
<p>When I first joined the project the muscle of GPUI2 was still forming on the skeleton, and the extent of its rendering capabilities was displaying some static, colored boxes and text in a window.</p>
<p>It's been awesome to see the progression from those humble beginnings to a full-blown editor that I can get work done in. On Monday we hit the major milestone of being able to hold our weekly all-hands meeting inside of a nightly build of Zed.</p>
<p>So far I've been having an absolute blast hacking on Zed with the rest of the <a href="https://zed.dev/team">team</a>, and I'm looking forward to what we'll build in 2024.</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01hhgpatq6c5kva2x6jjyz8st1" alt="A screenshot of some code in Zed Nightly." /></p>
Crane: Week One2023-06-18T04:04:36.989+00:002023-06-18T04:04:36.989+00:00Unknownhttps://maxdeviant.com/posts/2023/crane-week-one/<p>Today marks one week since I started working on my programming language, Crane.</p>
<h2 id="an-introduction">An Introduction</h2>
<p>Crane is a language for writing robust and elegant software that will endure over time.</p>
<p>It's statically typed, compiles to native code with an (eventual) garbage collector<sup class="footnote-reference"><a href="#1">1</a></sup>, and comes with the features you'd expect of a modern language, like sum types and no nulls.</p>
<p>Crane currently supports the following types:</p>
<ul>
<li><code>()</code> - The unit type</li>
<li><code>Uint64</code> - A 64-bit unsigned integer</li>
<li><code>String</code> - An immutable string of UTF-8 characters</li>
</ul>
<p>There's also the minimal beginnings of a standard library that contains the following functions:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">print</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">value</span><span style="color:#89ddff;">:</span><span> String</span><span style="color:#89ddff;">) {}
</span><span>
</span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">println</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">value</span><span style="color:#89ddff;">:</span><span> String</span><span style="color:#89ddff;">) {}
</span><span>
</span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">int_add</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">a</span><span style="color:#89ddff;">:</span><span> Uint64, </span><span style="color:#f78c6c;">b</span><span style="color:#89ddff;">:</span><span> Uint64</span><span style="color:#89ddff;">) -></span><span> Uint64 </span><span style="color:#89ddff;">{}
</span><span>
</span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">int_to_string</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">value</span><span style="color:#89ddff;">:</span><span> Uint64</span><span style="color:#89ddff;">) -></span><span> String </span><span style="color:#89ddff;">{}
</span></code></pre>
<p>Here's an example of a small Crane program:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">main</span><span style="color:#89ddff;">() {
</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> world </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">世界</span><span style="color:#89ddff;">"
</span><span>
</span><span> </span><span style="color:#82aaff;">hello</span><span style="color:#89ddff;">(</span><span>world</span><span style="color:#89ddff;">)
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">hello</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">name</span><span style="color:#89ddff;">:</span><span> String</span><span style="color:#89ddff;">) {
</span><span> </span><span style="color:#82aaff;">print</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">你好,</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#82aaff;">print</span><span style="color:#89ddff;">(</span><span>name</span><span style="color:#89ddff;">)
</span><span> </span><span style="color:#82aaff;">println</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">。</span><span style="color:#89ddff;">")
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>Running this through the Crane compiler will produce a single-file binary which we can then execute:</p>
<pre data-lang="sh" style="background-color:#212121;color:#eeffff;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#82aaff;">$ ./build/main
</span><span style="color:#82aaff;">你好,世界。
</span></code></pre>
<h3 id="compile-time-errors">Compile-time errors</h3>
<p>Being a statically-typed language, Crane will provide you with helpful errors at compile time when there are issues with your program:</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01h368z191b944dcnjp089xeej" alt="A Crane compile error: "Expected std::prelude::Uint64 but received std::prelude::String."" /></p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01h3693ph276p6ng455s6vb8qh" alt="A Crane compile error: "Function hallo does not exist."" /></p>
<p>Building excellent compiler errors is going to be a lot of work, but I think it's an important aspect of the language. By putting in a good diagnostic framework early my hope is that it will help make it easier to build with good errors in mind.</p>
<h3 id="syntax">Syntax</h3>
<p>If you're familiar with Rust at all, you might have noticed that Crane's syntax is decidedly Rust-like. This is no accident.</p>
<p>I think Rust gets a tremendous amount right as a language, both in terms of syntax and language features. The design of Crane owes a lot to Rust as an inspiration.</p>
<p>Of course, there are—and will continue to be—differences as Crane evolves and comes into its own.</p>
<h2 id="a-peek-under-the-hood">A Peek Under the Hood</h2>
<p>The Crane compiler is written in Rust. This was an obvious choice for me.</p>
<p>Rust is very well-suited to compiler development, and there is a <a href="https://github.com/Kixiron/rust-langdev">wealth</a> of crates available to help with the language-building process.</p>
<p>Some of the crates that power the Crane compiler:</p>
<ul>
<li><a href="https://crates.io/crates/clap"><code>clap</code></a> for powering the compiler CLI</li>
<li><a href="https://crates.io/crates/logos"><code>logos</code></a> for lexing</li>
<li><a href="https://crates.io/crates/ariadne"><code>ariadne</code></a> for reporting errors and diagnostics</li>
<li><a href="https://crates.io/crates/inkwell"><code>inkwell</code></a> for interacting with LLVM in the backend</li>
<li><a href="https://crates.io/crates/insta"><code>insta</code></a> for snapshot testing</li>
</ul>
<p>At time of writing the Crane codebase is just shy of 2,500 lines of code:</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01h368r4kdcec61phe3xgfghah" alt="cloc output for Crane. There are 2,356 lines of Rust code." /></p>
<hr />
<p>It's still very much early days for Crane and there's a long road ahead before it becomes a "real" language, but I'm very happy with the progress of this first week.</p>
<p>I expect to write more of these posts about Crane in the future.</p>
<p>In the meantime, you can check out the <a href="https://github.com/crane-lang/crane">Crane repo</a> on GitHub if you'd like to follow along with Crane's development.</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>Currently Crane programs just gratuitously leak memory.</p>
</div>
The Life You Knew Is Dead2023-05-13T01:06:29.124+00:002023-05-13T01:06:29.124+00:00Unknownhttps://maxdeviant.com/shards/2023/the-life-you-knew-is-dead/<p>Earlier this week I found myself scouring my house for the pen to my Wacom table. I never did find it, but what I did find was a box of keepsakes from my adolescence.</p>
<p>In this box was an assortment of things: the passbook for my first bank account with the Bank of China, some photos of me with my friends and classmates, letters for my future self given to me by my closest friend when I left China, and my old Motorola cellphone.</p>
<p>I put the battery back in the phone, charged it up, and turned it on. The once-familiar startup tone greeted me as I was prompted with a passcode input. My heart sank; surely there's no way I remember it?</p>
<p>It took a few tries, but somewhere deep in my subconscious I still knew that code by heart.</p>
<p>With the phone unlocked I started scrolling through the messages.</p>
<p>There aren't many on it, but the few that are there tell the story of my final days in China. Well, half the story. The sent messages have been recently cleared, so most of the messages in the inbox are missing their outbox equivalent.</p>
<p>Reading between the lines, it seems I must have just gotten back from Thailand. I'm trying to organize some time for us to hang out, but schedules are complicated. One of my friends is also flying out a day or two before me and doesn't have time to hang out. My other friend isn't allowed to speak to me (it's a long story).</p>
<p>Many of the texts are just logistics. It looks like I'm trying to make it to the airport to see someone off before I possibly never see them again.</p>
<p>The last two texts on the phone are both from the same person.</p>
<p>The first:</p>
<blockquote>
<p>rise and shine bro!</p>
<p>07:19am 06/20/10</p>
</blockquote>
<p>This friend had come over to help us get our luggage down from the apartment and to the van waiting outside of the complex.</p>
<p>The second text, received as we drove away, airport-bound:</p>
<blockquote>
<p>c ya!</p>
<p>07:37am 06/20/10</p>
</blockquote>
<p>This was the last message I ever received on that phone.</p>
<hr />
<p>I felt like an archaeologist reading through stories etched into a piece of stone. A relic uncovered from the ashes of a place long-forgotten, but whose memory lives on through these recovered artifacts. These stories are etched into flash memory instead of stone tablets, but etched they are all the same.</p>
<p>And just as a volcanic eruption could lay an entire ancient city to ruin in an instant, so too did my life get buried before my very eyes.</p>
<p>In just over a month it will have been thirteen years since that final "c ya!", and I still don't think I've fully come to terms with the loss I experienced that day.</p>
<p>Thirteen years since I've set foot on Chinese soil.</p>
<p>Thirteen years since I've spoken to the majority of the friends I had while I was there.</p>
<p>Thirteen years tinged with the regret of taking my life there for granted.</p>
<p>When turning <a href="https://maxdeviant.com/shards/2022/28/">28</a> I wrote:</p>
<blockquote>
<p>27 has been marked by the crushing weight of my own existence.</p>
</blockquote>
<p>The crushing weight I speak of is this burden of grief that I've been shouldering for the past thirteen years. All this time it's been weighing on me, pushing my body and soul down into the dust.</p>
<hr />
<p>As I sat there reading through my phone and thinking about that period in my life, it made me want to reconnect with my old friends from back then. Despite not having spoken to most of them in over a decade, a small part of me wants to believe that one small "hey, how's it going?" is enough to pick up right where we left off.</p>
<p>This, of course, is wishful thinking.</p>
<p>With so much time passed and during incredibly formative years at that, it's unreasonable to expect that any of the friends I once knew are going to be the same as how I remember them.</p>
<p>These are living, breathing people who have changed, as I have, and are not confined to this snapshot of them that I cling to in the hopes that it can take me back to a time I ache for.</p>
<p>I had this same realization in <a href="https://maxdeviant.com/shards/2022/returning-to-games/">"Returning to Games"</a>:</p>
<blockquote>
<p>As I've thought about this, I now realize that these experiences with video games are just a stand-in for a deeper truth: there are some things in my life I can never return to in the way I want.</p>
</blockquote>
<p>I spent some time on Facebook tonight browsing through some of the profiles of old friends of mine.</p>
<p>Many—like me—have all but abandoned the platform, their profiles devoid of activity except for a one-way barrage of birthday wishes once a year. Others have deleted their accounts entirely.</p>
<p>There are some who are active, posting photos of their travels or their kids; the sorts of things you'd expect.</p>
<p>However, what I wasn't expecting were the ones that truly exhibited this rejection of my memorialized versions of them.</p>
<p>On one person's profile I found a slew of pro-Trump and anti-masking posts and links to articles from far-right websites. Another was reposting QAnon material and promoting an <a href="https://www.adl.org/glossary/fall-cabal">antisemitic documentary</a>.</p>
<p>These divergences from my idyllic mental picture came as quite the surprise. While these are almost certainly statistical outliers, I feel they are, in a way, indicative of the change I'd find were I to talk to any one of my old friends for more than a few minutes.</p>
<p>As a current friend of mine put it:</p>
<blockquote>
<p>talking to childhood friends is pretty awkward past the first few minutes of discussing what people are doing for work and maybe meming a bit about the old days</p>
</blockquote>
<p>After running through the routine factual catch-up and a bit of reminiscing about the "good old days", I suspect it would be rare for me to have much in common with anyone.</p>
<hr />
<p>This excursion has confronted me with a truth I should have come to terms with a long time ago:</p>
<p>The life I had—the life I knew—is dead.</p>
<p>It died the moment I climbed into that van headed for the airport.</p>
<p>All these years I've been holding onto memories of that life and thinking that there was some way to make it a reality again. But the reality is that memories is all they are; memories of a time that will never be again.</p>
<p>For a long time now I've been able to fool myself into thinking otherwise. If I never open the box, then I can keep on living in ignorance to whether the cat—or in this case, an idealized snapshot of my youth—was alive or dead.</p>
<p>Now I've opened up the box, and from that there's no going back.</p>
<p>I still don't know how to feel about this, but it feels like I'm moving in the right direction.</p>
<p>Onwards.</p>
Alphabetization Soup2023-01-02T19:34:41.215+00:002023-01-02T19:34:41.215+00:00Unknownhttps://maxdeviant.com/posts/2023/alphabetization-soup/<p>Something I've seen come up—typically in code review—in the past year is the alphabetization of different constructs within source code.</p>
<p>While alphabetization can be used to provide consistency within a codebase, over-application of it has the potential to actually make code <em>worse</em>.</p>
<h2 id="why-alphabetize">Why alphabetize?</h2>
<p>Let me preface this with the caveat that alphabetizing constructs in your code should be the computer's job. If you are making the stylistic choice to sort things alphabetically and don't have this as an "autofix" option in your formatter or linter, perhaps consider forgoing this until your tooling can handle it for you.</p>
<p>Now that we know that your computer will be doing all the drudgery of reordering constructs on your behalf, let's talk about why you might want to do this in the first place.</p>
<p>Alphabetizing your code can lead to greater consistency throughout the codebase. If certain constructs are always inserted in the same order, it removes the possibility that two different developers will make a different choice about where an addition will go. This can also help keep diffs clean, as additions will follow a deterministic order.</p>
<p>Another cited benefit is that alphabetizing can reduce the cognitize overhead for developers. If a list of items is alphabetized, it requires little thought to know where to insert a new item.</p>
<h2 id="case-studies">Case studies</h2>
<p>Let's take a look at some different scenarios that I've seen out in the wild and how alphabetization plays out for each of them.</p>
<h3 id="object-fields">Object fields</h3>
<p>Suppose we have the following <code>Customer</code> type:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>Customer </span><span style="color:#89ddff;">{
</span><span> id</span><span style="color:#89ddff;">: </span><span>CustomerId</span><span style="color:#89ddff;">;
</span><span> username</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> fullName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> age</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">number</span><span style="color:#89ddff;">;
</span><span> address</span><span style="color:#89ddff;">: </span><span>Address</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>As it stands, we have the fields arranged in a meaningful order:</p>
<ul>
<li>The customer's ID comes first, as that is what we use to uniquely identify the record within the system</li>
<li>Next we have customer's username and full name, which are both additional identifiers</li>
<li>Finally we have some ancillary metadata, like the customer's age and address</li>
</ul>
<p>The important thing here is that the author has the agency to organize the fields in a way that helps encode additional meaning into the source code.</p>
<p>Of course, there is some subjectivity here. Who's to say that <code>address</code> shouldn't come before <code>age</code>?</p>
<p>In this case, I think the <em>grouping</em> of similar fields is more important than their <em>ordering</em> with respect to each other.</p>
<p>If we were to arrange the fields within the <code>Customer</code> type alphabetically it would look this:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>Customer </span><span style="color:#89ddff;">{
</span><span> address</span><span style="color:#89ddff;">: </span><span>Address</span><span style="color:#89ddff;">;
</span><span> age</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">number</span><span style="color:#89ddff;">;
</span><span> fullName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> id</span><span style="color:#89ddff;">: </span><span>CustomerId</span><span style="color:#89ddff;">;
</span><span> username</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>While this ordering may seem fine (although I still maintain that it's <em>weird</em> to not have <code>id</code> come first), there are other cases where sorting fields alphabetically just doesn't make sense.</p>
<p>Consider this common definition of a <code>Rectangle</code> object that you might see in graphics programming:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>Rectangle </span><span style="color:#89ddff;">{
</span><span> width</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">number</span><span style="color:#89ddff;">;
</span><span> height</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">number</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>It is standard to talk about rectangles as "width by height", so by keeping the fields in the same order within the code we're keeping things aligned with the real world.</p>
<p>If we were sort these fields alphabetically, we'd end up with this backwards ordering:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>Rectangle </span><span style="color:#89ddff;">{
</span><span> height</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">number</span><span style="color:#89ddff;">;
</span><span> width</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">number</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>There are also situations where the ordering of fields within an object has actual implications at runtime. When using <code>repr(C)</code> in Rust the order of the fields determines their order in memory:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#89ddff;">#[</span><span>repr</span><span style="color:#89ddff;">(</span><span>C</span><span style="color:#89ddff;">)]
</span><span style="font-style:italic;color:#c792ea;">struct </span><span>ThreeInts </span><span style="color:#89ddff;">{
</span><span> some_int</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">i16</span><span>,
</span><span> just_an_int</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">i8</span><span>,
</span><span> another_int</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">i32
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>If we were to sort the struct fields alphabetically it would change the memory layout of the <code>ThreeInts</code> struct, and potentially break compatibility with an external program:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#89ddff;">#[</span><span>repr</span><span style="color:#89ddff;">(</span><span>C</span><span style="color:#89ddff;">)]
</span><span style="font-style:italic;color:#c792ea;">struct </span><span>ThreeInts </span><span style="color:#89ddff;">{
</span><span> another_int</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">i32
</span><span> just_an_int</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">i8</span><span>,
</span><span> some_int</span><span style="color:#89ddff;">: </span><span style="font-style:italic;color:#c792ea;">i16</span><span>,
</span><span style="color:#89ddff;">}
</span></code></pre>
<p><strong>Conclusion:</strong> Don't alphabetize fields in objects.</p>
<h3 id="import-statements">Import statements</h3>
<p>Let's take a look at another scenario: import statements.</p>
<p>In most modern tooling, import statements are, by and large, written by your editor. You start typing the name of a function you want to use, and your editor will helpfully suggest an import for you and insert it into the import list.</p>
<p>Because the computer is already the primary author of the import statements in the first place, it makes sense for it to also be in charge of keeping them tidy.</p>
<p>It's worth calling out that the ordering of import statements is not always strictly alphabetical.</p>
<p>In JavaScript and TypeScript, for example, it's common to sort relative imports after absolute imports:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>fromAnotherExternalPackage </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">another-external-package</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>fromExternalPackage </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">external-package</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span>fs </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">node:fs</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>fromSuperParent </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">../../super-parent</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>fromParent </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">../parent</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>fromSibling </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./sibling</span><span style="color:#89ddff;">';
</span></code></pre>
<p>Similarly, a common idiom in Rust is to create three separate groups of imports:</p>
<ul>
<li><code>std</code>, <code>core</code>, and <code>alloc</code></li>
<li>external crates</li>
<li><code>self</code>, <code>super</code>, and <code>crate</code> imports</li>
</ul>
<p>Each group is delineated by a blank line, and imports are then sorted alphabetically within each group:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#c792ea;">use </span><span>alloc</span><span style="color:#89ddff;">::</span><span>alloc</span><span style="color:#89ddff;">::</span><span>Layout</span><span style="color:#89ddff;">;
</span><span style="color:#c792ea;">use </span><span>core</span><span style="color:#89ddff;">::</span><span>f32</span><span style="color:#89ddff;">;
</span><span style="color:#c792ea;">use </span><span>std</span><span style="color:#89ddff;">::</span><span>sync</span><span style="color:#89ddff;">::</span><span>Arc</span><span style="color:#89ddff;">;
</span><span>
</span><span style="color:#c792ea;">use </span><span>broker</span><span style="color:#89ddff;">::</span><span>database</span><span style="color:#89ddff;">::</span><span>PooledConnection</span><span style="color:#89ddff;">;
</span><span style="color:#c792ea;">use </span><span>chrono</span><span style="color:#89ddff;">::</span><span>Utc</span><span style="color:#89ddff;">;
</span><span style="color:#c792ea;">use </span><span>juniper</span><span style="color:#89ddff;">::{</span><span>FieldError</span><span style="color:#89ddff;">,</span><span> FieldResult</span><span style="color:#89ddff;">};
</span><span style="color:#c792ea;">use </span><span>uuid</span><span style="color:#89ddff;">::</span><span>Uuid</span><span style="color:#89ddff;">;
</span><span>
</span><span style="color:#c792ea;">use super</span><span style="color:#89ddff;">::</span><span>schema</span><span style="color:#89ddff;">::{</span><span>Context</span><span style="color:#89ddff;">,</span><span> Payload</span><span style="color:#89ddff;">};
</span><span style="color:#c792ea;">use super</span><span style="color:#89ddff;">::</span><span>update</span><span style="color:#89ddff;">::</span><span>convert_publish_payload</span><span style="color:#89ddff;">;
</span><span style="color:#c792ea;">use crate</span><span style="color:#89ddff;">::</span><span>models</span><span style="color:#89ddff;">::</span><span>Event</span><span style="color:#89ddff;">;
</span></code></pre>
<p>These rules are <a href="https://rust-lang.github.io/rustfmt/?version=v1.5.1&search=import#StdExternalCrate%5C%3A">automatable</a> with <code>rustfmt</code>, meaning the author doesn't need to spend any time thinking about how to order the imports.</p>
<p>This is a good example of how code can be alphabetized while still retaining meaning. The categorization of imports into these separated groups improves the readability of the code by making them easily distinguishable at a glance.</p>
<p><strong>Conclusion:</strong> Alphabetize your imports!</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>This post does not intend to be an exhaustive look at all of the different scenarios and whether alphabetization does or or doesn't make sense for each one.</p>
<p>Rather, I wanted to provide some examples of how to weigh the pros and cons of alphabetization in different contexts that can be extended to different situations.</p>
<p>Ultimately, my position is that it's useful to able to imbue code with additional meaning through the intentional grouping and ordering of constructs, and that this takes precedence over consistency for consistency's sake or reducing some already minimal cognitive overhead.</p>
<p>There are some cases—like imports—where alphabetization is a no-brainer, but I think these cases are moreso the exception than the rule.</p>
Hello, 20232023-01-02T04:08:32.475+00:002023-01-02T04:08:32.475+00:00Unknownhttps://maxdeviant.com/shards/2023/hello-2023/<p>Over the past few years I've done a terrible job of doing year-in-reviews and setting goals for the new year.</p>
<p>This is me trying to do better.</p>
<p>Some goals for 2023:</p>
<ul>
<li>Write Rust, and a lot of it</li>
<li>Distance myself from platforms I don't control</li>
<li>Connect with others more intentionally</li>
<li>Do better at planning and cooking meals</li>
<li>Find a form of exercise that I don't hate</li>
</ul>
Devlog: Today I Did2022-11-12T23:19:41.157+00:002022-11-12T23:19:41.157+00:00Unknownhttps://maxdeviant.com/posts/2022/devlog-today-i-did/<p>We use <a href="https://threads.com/">Threads</a><sup class="footnote-reference"><a href="#1">1</a></sup> pretty heavily at WorkOS. One of the things we use it for is to run async standups.</p>
<p>I found myself exerting more effort than I would have liked compiling my standup updates, specifically around wrangling the URLs to PRs and <a href="https://linear.app/">Linear</a> issues that I had worked on that day.</p>
<p>This process involved massive amounts of drudgery in the form of clicking through various PRs on GitHub, copying their URLs and the URLs of any attached Linear issues, and then transcribing these into Threads.</p>
<p>In a small win for <a href="https://brandur.org/fragments/slack-bar-raising">bar-raising</a>, Threads supports copy/pasting Markdown into their rich-text editor and translating it into their rich-text format. This was a small improvement over using the built-in WYSIWYG editor directly, as I could compose in Markdown in a text buffer and then copy it in with the right formatting. However, I still had the problem of having to curate this Markdown by hand.</p>
<p>This seemed like a problem that could be solved trivially with a bit of code, and thus the first version of <code>today-i-did</code> was born.</p>
<h2 id="the-workflow">The Workflow</h2>
<p>The workflow for <code>today-i-did</code> looks something like this:</p>
<p>I keep a <code>TODAY.md</code> file in my home directory that I use to edit my daily standup notes. I compose them in Markdown and jot down the URLs of the PRs I've worked on that day. When I'm done, it might resemble something like this:</p>
<pre data-lang="md" style="background-color:#212121;color:#eeffff;" class="language-md "><code class="language-md" data-lang="md"><span style="color:#4a4a4a;">## </span><span style="color:#c3e88d;">What are you working on?
</span><span>
</span><span>- Added examples for Directory Sync operations </span><span style="text-decoration:underline;color:#f78c6c;">https://github.com/workos/workos-rust/pull/70
</span><span>- Renamed </span><span style="color:#4a4a4a;">`</span><span style="color:#c792ea;">VerifyFactor</span><span style="color:#4a4a4a;">`</span><span> to </span><span style="color:#4a4a4a;">`</span><span style="color:#c792ea;">VerifyChallenge</span><span style="color:#4a4a4a;">` </span><span style="text-decoration:underline;color:#f78c6c;">https://github.com/workos/workos-rust/pull/71
</span><span>
</span><span style="color:#4a4a4a;">## </span><span style="color:#c3e88d;">What's up next?
</span><span>
</span><span>- Continue work on Rust SDK
</span></code></pre>
<p>Then, I run the <code>TODAY.md</code> file through <code>today-i-did</code> and it outputs the following to stdout:</p>
<pre data-lang="md" style="background-color:#212121;color:#eeffff;" class="language-md "><code class="language-md" data-lang="md"><span style="color:#4a4a4a;">## </span><span style="color:#c3e88d;">What are you working on?
</span><span>
</span><span>- Added examples for Directory Sync operations (</span><span style="color:#4a4a4a;">[</span><span>SDK-539</span><span style="color:#4a4a4a;">](</span><span style="text-decoration:underline;color:#f78c6c;">https://linear.app/workos/issue/SDK-539/add-examples-for-directory-sync-operations</span><span style="color:#4a4a4a;">)</span><span>, </span><span style="color:#4a4a4a;">[</span><span>PR 70</span><span style="color:#4a4a4a;">](</span><span style="text-decoration:underline;color:#f78c6c;">https://github.com/workos/workos-rust/pull/70</span><span style="color:#4a4a4a;">)</span><span>)
</span><span>- Renamed </span><span style="color:#4a4a4a;">`</span><span style="color:#c792ea;">VerifyFactor</span><span style="color:#4a4a4a;">`</span><span> to </span><span style="color:#4a4a4a;">`</span><span style="color:#c792ea;">VerifyChallenge</span><span style="color:#4a4a4a;">`</span><span> (</span><span style="color:#4a4a4a;">[</span><span>SDK-540</span><span style="color:#4a4a4a;">](</span><span style="text-decoration:underline;color:#f78c6c;">https://linear.app/workos/issue/SDK-540/rename-verifyfactor-to-verifychallenge</span><span style="color:#4a4a4a;">)</span><span>, </span><span style="color:#4a4a4a;">[</span><span>PR 71</span><span style="color:#4a4a4a;">](</span><span style="text-decoration:underline;color:#f78c6c;">https://github.com/workos/workos-rust/pull/71</span><span style="color:#4a4a4a;">)</span><span>)
</span><span>
</span><span style="color:#4a4a4a;">## </span><span style="color:#c3e88d;">What's up next?
</span><span>
</span><span>- Continue work on Rust SDK
</span></code></pre>
<p>I can then copy this over to Threads, where I get a nicely-formatted status update:</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01ghpz6drvxj2ec9h7v8jbzd32" alt="A screenshot of the status update pasted into Threads" /></p>
<p>Under the hood the program scans the input file looking for GitHub PR URLs. When it finds them, it uses the GitHub API to pull down the comments on the PR to look for mentions of Linear issues by the Linear GitHub bot, which it then pulls down using the Linear API.</p>
<p>It then takes this information and formats it nicely as a series of Markdown links and swaps them in for the original GitHub PR URL.</p>
<h2 id="version-1">Version 1</h2>
<p>The first version of <code>today-i-did</code> was a small PureScript program, <code>cloc</code>ing in at 209 lines of PureScript and 37 lines of JavaScript <a href="https://github.com/purescript/documentation/blob/84e55747f33191bf1640c361dd1e662b3e092d5c/guides/FFI.md">FFI</a> code to interact with the GitHub and Linear SDKs.</p>
<p>Since both the GitHub and Linear SDKs are available as npm packages, it was fairly trivial to pull them in and call out to them over the PureScript FFI.</p>
<p>I've been using this version daily Monday through Friday since September 2021, and it's certainly saved me many hours at this point.</p>
<h2 id="purescript-rust">PureScript → Rust</h2>
<p>A couple weeks ago, I decided to rewrite <code>today-i-did</code> in Rust in order to make its usage more streamlined. After all, it's a lot easier to install and consume a single binary than having to drag around a bunch of—albeit transpiled—JavaScript and npm modules.</p>
<p>The rewrite came in at exactly 200 lines of Rust code, which is a testament to just how expressive Rust can be. Granted, there are still a few parts that aren't as elegant as the PureScript implementation. For one, the line replacement algorithm isn't quite a succinct as using <code>traverse</code>.</p>
<p>Most of the work for the rewrite actually came in the form of building a <a href="https://github.com/maxdeviant/linear-sdk">crate</a> to interact with the Linear GraphQL API. There's still some more work to do on it before it's fully functional, but it's complete enough to use for <code>today-i-did</code>.</p>
<p>This morning I wrapped up a few loose ends that had been bothering me. Up until now I've been relying on a <code>.env</code> file to provide the configuration, which has finally moved configuration out to a separate config file. This should make it more amenable to running as a binary available on my <code>$PATH</code>.</p>
<p>While this tool is very specific to my current needs, I wanted to put it out in the open in the off-chance that someone else gets some mileage out of it. With that said, the <a href="https://github.com/maxdeviant/today-i-did">repo</a> is public.</p>
<p>At the very least, maybe it will inspire you to solve your own problems.</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>The old version, not the one seen on their marketing site.</p>
</div>
The Sack of Twitter (2022)2022-11-06T00:00:00+00:002022-11-06T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0018-the-sack-of-twitter-2022/<p>Greetings, travelers.</p>
<p>It's been almost a full year since I've written one of these.</p>
<p>In case you've forgotten, this is <em>Errata Exist</em>, a newsletter containing my thoughts on software development, self improvement, and anything else that catches my fancy.</p>
<p>Coincidentally, the weather is in about the same state as when I wrote the previous issue of this newsletter:</p>
<blockquote>
<p>The weather has been ever-so-dreary here this week. This weekend, in particular, has really been exuding gloomy November vibes. SAD season is truly here.</p>
<p>This has left me with plenty of time to sit inside, drink tea, hack on various things, and ponder some of the finer things in life (like Church encoding).</p>
<p>— <a href="https://maxdeviant.com/newsletters/errata-exist/0017-church-encoding-and-legos/"><em>Errata Exist, x0017</em></a></p>
</blockquote>
<p>Yesterday was a beautiful day, although perhaps not quite fitting for the season: sunny and 75ºF/24ºC.</p>
<p>This morning is decidedly more autumnal, with a crisper air, the scent of fresh rain, and a haze overhead that tints the world around it gray. It does create quite a stark contrast between the red and yellow leaves—many of which are still clinging to the trees—and the gray sky that silhouettes them.</p>
<h2 id="falling-back">Falling Back</h2>
<p>Last night was the clock change, with us falling back to Eastern Standard Time (EST) during the night. Ideally we would get rid of daylight saving time entirely and just stay on standard time year-round. Unfortunately, it seems that the <a href="https://www.reuters.com/world/us/us-senate-approves-bill-that-would-make-daylight-savings-time-permanent-2023-2022-03-15/">efforts</a> to eliminate daylight saving time are insistent on taking the worse of the two options, that is, the one that would make it permanently Eastern Daylight Time (EDT) for me.</p>
<p>Perhaps the only upside to EDT is that it makes doing conversions to China Standard Time easier, as it's always exactly twelve hours ahead.</p>
<h2 id="the-sack-of-twitter-2022">The Sack of Twitter (2022)</h2>
<p><img src="https://drop.maxdeviant.com/drops/drop_01gh6sz7g5en5efk8kq2en3gbs" alt="The Course of Empire: Destruction by Thomas Cole" /></p>
<blockquote>
<p><em>The Course of Empire: Destruction</em> by Thomas Cole</p>
</blockquote>
<p>Over the past week I've watched in horror at the happenings over at Twitter HQ after Elon Musk took control. It is, perhaps, the textbook definition of a "tire fire".</p>
<p>While not intended to be a complete recounting of all that has transpired thus far, here are some key events:</p>
<ul>
<li>We've seen an estimated 50% of the staff laid off, including the entire accessibility team</li>
<li>The folks remaining have had to crunch on shipping a revamp to the "verified" checkmarks, with some even sleeping in their offices over the weekend</li>
<li>Mastodon has seen a surge in account creation as people jump ship or make preparations to</li>
</ul>
<p>As far as social networking sites go, Twitter has been a constant for me. I've had my account since 2009 and have amassed 51.3k tweets (the majority of which are retweets) over that time.</p>
<p>With the likes of Tumblr, Instagram, and Facebook all having descended into <a href="https://maxdeviant.com/shards/2021/tumblr-the-barren-wasteland/">barren wastelands</a>, Twitter has been the last major stronghold of my social media usage. With that stronghold now being sacked, it begs the question "what now?"</p>
<p>Many of the people I follow have been jumping ship to Mastodon and/or Cohost. I already have accounts on both platforms and as I've seen folks link to their accounts in my Twitter timeline I've been following them on other platforms.</p>
<p>For my part, I haven't dropped Twitter entirely yet, but am prepared to do so at a moment's notice. I've already exported my Twitter archive and backed my tweets up.</p>
<p>One thing that I've noticed is that I don't feel the same amount of excitement for Mastodon or Cohost that I've felt with Twitter. It may just be that Twitter is familiar and the blank slate of a new platform is scary. Or maybe it's something else?</p>
<p>As I've been exploring this feeling over the past week, it's occurred to me that my usage of Twitter may just be on autopilot. This is, perhaps, best exemplified by my tweet to retweet ratio. So much of my Twitter usage is just retweeting things from other accounts, something that is incredibly consumptive and requires minimal brain power on my part.</p>
<p>Rather than immediately finding some other platform to fill the space that Twitter may leave in its wake, this is my chance to take a step back and re-evaluate what it is I want to get out of being on these platforms.</p>
<p>I'd like to take this opportunity to focus more on writing on <a href="https://maxdeviant.com/">my site</a> as well as this newsletter.</p>
<h2 id="in-other-news">In Other News</h2>
<p>This past week I celebrated my two-year anniversary at WorkOS, and I wrote down some <a href="https://maxdeviant.com/posts/2022/two-years-at-workos/">thoughts</a> on the journey so far.</p>
<hr />
<p>That's all for now!</p>
<p>I look forward to writing again soon,</p>
<p>Marshall</p>
Two Years at WorkOS2022-11-03T12:00:00+00:002022-11-03T12:00:00+00:00Unknownhttps://maxdeviant.com/posts/2022/two-years-at-workos/<p>Today is my two-year anniversary at WorkOS.</p>
<p>I missed writing down my thoughts on my first anniversary, so it's time to turn back the clock a ways and explain how I ended up here.</p>
<h2 id="in-search-of-single-sign-on">In Search of Single Sign-On...</h2>
<p>Back in early 2020 I was working at Gravic. At the time, we were trying to land some site deals with universities, many of which were contingent on them being able to sign into our product using <a href="https://en.wikipedia.org/wiki/Single_sign-on">single sign-on (SSO)</a>.</p>
<p>I ended up with the task of figuring out how to add SSO to our product.</p>
<p>As I began to research the space, it seemed there were two options:</p>
<ol>
<li>Build out SSO ourselves, which would probably take months just for one provider</li>
<li>Outsource our entire user management layer to something like Auth0 or AWS Cognito in order to leverage their SSO support</li>
</ol>
<p>Neither of these options seemed particularly attractive.</p>
<p>In the midst of facing this conundrum, <a href="https://twitter.com/jmduke/status/1239946383878053889">a tweet</a> from Justin Duke<sup class="footnote-reference"><a href="#1">1</a></sup> appeared in my Twitter feed:</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01ggtz8xbt4k4w87nyzbmdp723" alt="@jmduke: "it's relatively rare that i read a splash page and get so immediately convinced that a company is going to just print money" workos.com" /></p>
<p>Intrigued, I clicked through to <a href="https://workos.com">workos.com</a> and my eyes lit up as I realized a third option had presented itself: we could use WorkOS for SSO.</p>
<p>Over the next few weeks I put together a proposal and proceeded to add SSO to our system, using WorkOS for all of the hard bits. It was painless.</p>
<h2 id="joining-workos">Joining WorkOS</h2>
<p>Fast forward a few months and I was ready for something new. With COVID still raging and a mandatory return to the office looming in the new year, I decided to start searching for greener—and more remote—pastures.</p>
<p>Having used WorkOS to solve my own problems, I knew firsthand just how much value the product brings to the table. I'd also interacted with some of the folks at WorkOS over the course of my integration and had a really positive experience doing so.</p>
<p>With that in mind, I decided to apply to WorkOS.</p>
<p>This was my first time interviewing at a company, so to say I was nervous would be an understatement. Thankfully, WorkOS had (and still has) an excellent, no-nonsense interview process. Not once in the process did I feel like I was being evaluated unfairly or being asked to do something that didn't reflect what I'd be doing day-to-day<sup class="footnote-reference"><a href="#2">2</a></sup>.</p>
<h2 id="two-years-and-counting">Two Years and Counting</h2>
<p>It's now been two years since my first official day in our lovely virtual office.</p>
<p>In that time I've worn many hats and worked on everything from signup and billing flows to migrating databases between providers with zero downtime.</p>
<p>I've seen colleagues come and go, watched the company grow and change as more faces fill up the Zoom gallery in our weekly All Hands, and done some of the best work of my career thus far.</p>
<p>I'm consistently impressed by just how talented the people I get to work with everyday are. You don't have to look much further than the <a href="https://workos.com/docs">brand new docs</a> that launched earlier this year to see mastery of craft and an incredible eye for detail.</p>
<p>Of course, talent alone is not what makes WorkOS great. Underpinning everything is kindness, something that is reflected in one of our operating principles: "Be Kind".</p>
<p>I consider myself blessed to have the opportunity to work alongside such kind and talented individuals. I try my best not to take that for granted.</p>
<p>The past two years at WorkOS have been the best kind of adventure, and I'm looking forward to many more.</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>Creator of <a href="https://buttondown.email/">Buttondown</a>, a <em>stellar</em> newsletter service</p>
</div>
<!---->
<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup>
<p>I'm glad I didn't have to <a href="https://twitter.com/mxcl/status/608682016205344768">invert a binary tree on a whiteboard</a></p>
</div>
Is Forgetting the Human Experience?2022-09-26T03:15:42.006+00:002022-09-26T03:15:42.006+00:00Unknownhttps://maxdeviant.com/shards/2022/is-forgetting-the-human-experience/<p>The things I want to remember are rarely so apparent in the moment. It isn't until later, sometimes decades later, that I realize the importance of the small things that I want to hold on to forever.</p>
<p>For as long as I can remember my mother has been intent on making sure we get family photos whenever we get together as a family. I'll admit that for a while I found this annoying; why does every holiday need to be documented with a posed photo?</p>
<p>However, in the past few years I've found myself able to relate to the act of taking these photos more and more. After all, how many of us will remember these gatherings in twenty years without the photos to look back in?</p>
<p>The other night I was laying awake in bed, unable to fall asleep. As I laid there, my mind drifted back to my family's old apartment in Kunming. I imagined myself going back and finding it just the way we left it.</p>
<p>I reach into my pocket and pull out the key. I insert it into the security door and turn it once, twice, three times to undo all the deadbolts. The fourth turn draws back the latch, and I lean my weight into the door to allow the latch to retract without getting stuck.</p>
<p>The door swings open and I'm greeted by the familiar sight of the hardwood floors. The afternoon sun beaming in through the large bay window fills the apartment with a warm glow. I leave my shoes at the door as I step inside.</p>
<p>At this point I must have fallen asleep, as I don't really remember much else of my time in the apartment. All I know is that somewhere in the deep recesses of my mind I can go through that metal, teal-colored door and exist in that space once again.</p>
<p>But as soon as I wake up, the details are hazy and indiscernible.</p>
<p>I have to wonder if this forgetting is a quintessential component of the human experience? Am I asking too much in wanting to remember these small—perhaps insignificant—moments from my past, or does everyone else suffer from forgetting moments like these?</p>
<p>I worry that these feelings will only grow as I age, with more things to remember and a brain that is increasingly unable to remember them all. The part that stings the most is that some of these moments have likely already been lost to time, forever.</p>
<p>Perhaps I am a hoarder. Not of physical possessions that would earn me a spot on A&E's show, but of memories. I'm loathe to forget all but the most painful of memories that I would freely and specifically relinquish to time's gaping maw.</p>
282022-08-24T02:20:56.233+00:002022-08-24T02:20:56.233+00:00Unknownhttps://maxdeviant.com/shards/2022/28/<p>I turn 28 tomorrow.</p>
<p>It feels weird to say that.</p>
<p>Something I've noticed over the past year is just how <em>old</em> I feel. I'll jerk a part of my body too quickly and suddenly it will be sore for the rest of the day (or longer, if I'm unlucky).</p>
<p>My health in general has been weighing on my mind as of late. My wife and I purchased a stationary bike recently and I've taken to riding that. Right now I'm doing it three times a week for about thirty minutes. My plan is to work my way up to daily rides.</p>
<p>I always forget how good I feel after exercising. I'll wake up in the morning and have no desire to get out of bed and get on the bike. But once I finish my ride and the post-workout endorphins have kicked in I ask myself why it was so hard to get on the bike in the first place. And yet, without fail the cycle repeats itself each time.</p>
<p>I think this is where the routine of exercising comes in. If I wake up every morning and hop on the bike, eventually—at least, I hope—it will just be a given and not something my mind will try to bargain with me over.</p>
<p>Another feeling I've had over the past year is this ever-present sense of urgency to everything. I don't fully know where this comes from, but it really pervades every corner of my life. I'll be out on an afternoon walk with the dog and have this visceral awareness of time passing me by. Or I'll be washing the dishes after dinner and feel like, much like the dishwater, that my time is being washed down the drain.</p>
<p>Speaking of time, it seems to be passing by me at a breakneck pace. What feels like five minutes has really been fifteen, a week is gone in an instant, and the months come and go before I feel they're halfway through.</p>
<p>Is this what growing old is? The realization that your time on this planet is finite and that your expiration date—once so imperceptibly far off in the future—is now looming on the horizon as you hurtle towards it at an accelerating pace?</p>
<p>I think the best way to sum it up is this:</p>
<p>27 has been marked by the crushing weight of my own existence.</p>
<hr />
<p>So where does that leave me?</p>
<p>As I'm deep in the throes of grappling with these feelings, I find myself starting to question what really matters and what I want out of my life.</p>
<p>The things I want to do could probably fill a multitude of lifetimes. Seeing as I'm stuck with just one, the only recourse is to brutally and relentlessly cut out everything that doesn't carry its weight.</p>
<p>I see 28 as a time to refocus on the things I think are truly important.</p>
Living Without Internet2022-08-13T04:22:52.615+00:002022-08-13T04:22:52.615+00:00Unknownhttps://maxdeviant.com/shards/2022/living-without-internet/<p>There was a time where I lived without internet.</p>
<p>That's a lie.</p>
<p>I wasn't <em>completely</em> without internet, but the internet was something that I only had access to when sitting at my desk on the computer. When I was hanging out with my friends or out around town I was completely detached from it.</p>
<p>Perhaps it was the naiveté of never having tasted unfettered access to the internet, but these felt like simpler times.</p>
<p>These days I find it hard to fathom not being online all of the time. I stream my music over Spotify instead of having a local iTunes library. Many of my conversations with others takes place on Discord or Telegram, not SMS.</p>
<p>Having a constant means of accessing the internet is a powerful tool.</p>
<p>When I visited Tokyo it was a rented mobile WiFi hotspot that made the trip possible. Having Google Maps and Google Translate at my fingertips allowed me to navigate the city with ease and overcome the language barrier.</p>
<p>Having readily-available internet access also feels like a curse.</p>
<p>I spend far too much time doom-scrolling on social media or news websites. There are vast expanses of the web just waiting to be explored and yet I frequent the same vapid haunts day after day with nothing lasting to show for it.</p>
<p>This small rectangle that lives in my pocket could be a crystal ball: something to gaze into to divine some meaning of the world around me. Yet all too often it feels more akin to a black hole that draws in anything that strays too close and never releases it.</p>
Blurred Platforms2022-07-26T03:29:07.637+00:002022-07-26T03:29:07.637+00:00Unknownhttps://maxdeviant.com/shards/2022/blurred-platforms/<p>In October 2013 Snapchat first launched stories. As a Snapchat user at the time, I took stories at their face value: a new feature on a platform I used, and nothing more.</p>
<p>What I didn't realize at the time was just how much I would come to loathe "stories" over the following years.</p>
<p>Before long, it seemed like every other platform was launching their own take on stories. Instagram and Facebook added stories of their own in 2016 and 2017, respectively.</p>
<p>Even at the time these seemed like blatant rip-offs in an attempt to keep up with the competition. Despite this, the addition of stories to both of these apps didn't feel too outlandishly out of place.</p>
<p>Things started to go off the rails in 2020 when Spotify decided to add stories to their platform. Why does a music streaming platform need stories? Hell if I know. But there they were.</p>
<hr />
<p>The year is now 2022 and much to my utter annoyance TikTok has taken over the world.</p>
<p>My (otherwise lovely) wife is just one of the many TikTok victims that exist. In turn, this makes me a secondhand TikTok victim, having to suffer through hearing the same "sounds" over, and <em>over</em>, and <em><strong>over</strong></em> again in the course of any given week.</p>
<p>TikTok first rose to prominence in the West after their acquisition of Musical.ly in 2018, and it wasn't long before other platforms started copying it.</p>
<p>Much like the Snapshot stories epidemic of the 2010s, the internet is now suffering from the TikTok cloning pandemic.</p>
<p>August 2020 saw Instagram—seemingly always the first to jump at a chance to rip-off another platform—launch Instagram Reels: a direct TikTok clone.</p>
<p>YouTube followed suit in July 2021 with the introduction of YouTube Shorts.</p>
<hr />
<p>The commonality between Snapshot stories and TikTok is this: all of these online platforms are blurring into one another. If you squint just a little, they are practically indistinguishable.</p>
<p>For all my disenchantment with online platforms of late, <a href="https://maxdeviant.com/shards/2022/cohost/">Cohost</a> is the one that has given me the most hope, for the sole reason that they are trying to do something <em>different</em>.</p>
<p>The internet needs more distinct platforms and fewer blurred ones.</p>
Static Typing Has Ruined Me2022-07-24T03:46:34.169+00:002022-07-24T03:46:34.169+00:00Unknownhttps://maxdeviant.com/shards/2022/static-typing-has-ruined-me/<p>There was a time in my life where I found static types burdensome.</p>
<p>The curricula for the programming classes in my senior year of high school and freshman year of college used Java as the language du jour. Java, being statically typed and (at the time) lacking in pretty much any semblance of type inference, required type annotations <em>everywhere</em>. There's only so many type annotations you can put in front of variables until the whole thing begins to feel like a massive drag.</p>
<p>This made JavaScript feel all the more like a breath of fresh air during my internship the following summer. Being able to write working code without having to denote any type information felt freeing. It certainly halped that my youthful hubris allowed me to sweep all the subsequent issues resulting from the lack of a static type system under the rug.</p>
<p>My honeymoon with dynamically-typed languages continued until my junior year of college when I was introduced to two new languages over the course of my "Programming Concepts and Paradigms" course. The first of these was Common Lisp: a fascinating language in its own right, but not the one that ultimately captured my heart.</p>
<p>That honor belongs to <a href="https://en.wikipedia.org/wiki/Standard_ML">Standard ML (SML)</a>.</p>
<p>Our final assignment for the class was to write traversal functions for a binary tree. The solution looked like this:</p>
<pre data-lang="ml" style="background-color:#212121;color:#eeffff;" class="language-ml "><code class="language-ml" data-lang="ml"><span>datatype 'a BinaryTree </span><span style="color:#89ddff;">=</span><span> empty </span><span style="color:#89ddff;">|</span><span> tree of 'a </span><span style="color:#89ddff;">*</span><span> 'a BinaryTree </span><span style="color:#89ddff;">*</span><span> 'a BinaryTree</span><span style="color:#89ddff;">;
</span><span>
</span><span>fun preOrder (tree(root, empty, empty)) </span><span style="color:#89ddff;">= [</span><span>root</span><span style="color:#89ddff;">]
</span><span style="color:#89ddff;">|</span><span> preOrder (tree(root, empty, right)) </span><span style="color:#89ddff;">=</span><span> root </span><span style="color:#89ddff;">::</span><span> preOrder right
</span><span style="color:#89ddff;">|</span><span> preOrder (tree(root, left, empty)) </span><span style="color:#89ddff;">=</span><span> root </span><span style="color:#89ddff;">::</span><span> preOrder left
</span><span style="color:#89ddff;">|</span><span> preOrder (tree(root, left, right)) </span><span style="color:#89ddff;">= [</span><span>root</span><span style="color:#89ddff;">] @</span><span> preOrder left </span><span style="color:#89ddff;">@</span><span> preOrder right</span><span style="color:#89ddff;">;
</span><span>
</span><span>fun inOrder (tree(root, empty, empty)) </span><span style="color:#89ddff;">= [</span><span>root</span><span style="color:#89ddff;">]
</span><span style="color:#89ddff;">|</span><span> inOrder (tree(root, empty, right)) </span><span style="color:#89ddff;">=</span><span> root </span><span style="color:#89ddff;">::</span><span> inOrder right
</span><span style="color:#89ddff;">|</span><span> inOrder (tree(root, left, empty)) </span><span style="color:#89ddff;">=</span><span> inOrder left </span><span style="color:#89ddff;">@ [</span><span>root</span><span style="color:#89ddff;">]
</span><span style="color:#89ddff;">|</span><span> inOrder (tree(root, left, right)) </span><span style="color:#89ddff;">=</span><span> inOrder left </span><span style="color:#89ddff;">@ [</span><span>root</span><span style="color:#89ddff;">] @</span><span> inOrder right</span><span style="color:#89ddff;">;
</span><span>
</span><span>fun postOrder (tree(root, empty, empty)) </span><span style="color:#89ddff;">= [</span><span>root</span><span style="color:#89ddff;">]
</span><span style="color:#89ddff;">|</span><span> postOrder (tree(root, empty, right)) </span><span style="color:#89ddff;">=</span><span> postOrder right </span><span style="color:#89ddff;">@ [</span><span>root</span><span style="color:#89ddff;">]
</span><span style="color:#89ddff;">|</span><span> postOrder (tree(root, left, empty)) </span><span style="color:#89ddff;">=</span><span> postOrder left </span><span style="color:#89ddff;">@ [</span><span>root</span><span style="color:#89ddff;">]
</span><span style="color:#89ddff;">|</span><span> postOrder (tree(root, left, right)) </span><span style="color:#89ddff;">=</span><span> postOrder left </span><span style="color:#89ddff;">@</span><span> postOrder right </span><span style="color:#89ddff;">@ [</span><span>root</span><span style="color:#89ddff;">];
</span></code></pre>
<p>Something clicked in my brain while writing this program. The simplicity and elegance of the traversal functions showed me that code can be beautiful. Additionally, this entire program is statically and strongly typed, but there isn't a type annotation in sight!</p>
<p>Since that day I've been sure and wholly ruined by static types. I'm loathe to write code in a dynamically-typed language, and in the times I'm forced to the experience is frustrating and fraught with errors. When working in any moderately-sized codebase I ask myself how anyone could make sweeping changes or refactorings in the absence of a static type system.</p>
<p>It seems that the industry at large has also realized that static types are a Good Thing<sup>TM</sup>, which is good news for me as I'd be a significantly less effective engineer without them.</p>
Returning to Games2022-07-18T03:37:26.610+00:002022-07-18T03:37:26.610+00:00Unknownhttps://maxdeviant.com/shards/2022/returning-to-games/<p>Returning to a video game after a hiatus is often a jarring experience.</p>
<p>Sadly, I am all too familiar with the experience of reinstalling a game in the hopes of rekindling a feeling it once evoked, only to immediately become overwhelmed after logging in.</p>
<h2 id="path-of-exile">Path of Exile</h2>
<p>I pretty much exclusively play Path of Exile during leagues. However, sometimes I'll log into old characters from past leagues to liquidate their items before deleting them to free up a character slot.</p>
<p>Typically this happens one or more patches after the character was originally created, which means that, upon logging in, the game presents you with a warning dialog that the character's passive skill tree has been reset. Since Path of Exile revolves so heavily around the passive tree, the impact of the reset cascades into the skill gems and gear on the character not being usable either.</p>
<p>These characters' builds are effectively bricked, especially since changes in the skill tree could make it impossible to run the same build.</p>
<p>You could always re-roll with a new, patch-appropriate build. This is a fine choice for testing out a new build, as you can skip the leveling process and jump straight into end-game.</p>
<h2 id="world-of-warcraft">World of Warcraft</h2>
<p>For a while I would return to World of Warcraft with each new expansion that released. What followed was a fairly predictable pattern: I would start a new character, level up to the new level cap, do a bit of end-game raiding, and then stop playing until the next expansion.</p>
<p>I would generally start by logging into an existing character with the intent of playing it for the new expansion, but expecting to pick up the character where I left off would always prove to be too lofty a dream. The hotbar setup that was once familar and comfortable is now indecipherable and clunky.</p>
<p>There also isn't any real downside to starting a new character. WoW expansions generally operate on invalidating all of the previous expansion's progress, at least in terms of gear. The green items you get from the quests in the new zones are already better than the top-tier raid drops from the last expansion.</p>
<p>Leveling up a new character allows you to be eased into a class, learn your skils gradually, and still end up at the same point gear-wise when you reach the end-game.</p>
<h2 id="league-of-legends">League of Legends</h2>
<p>I haven't played League of Legends in years (and I plan to keep it that way), but even if I did want to return to the game, I would be faced with a steep knowledge wall that would have to be broken through.</p>
<p>At my peak I had a decent understanding of each champion in the game: their movesets, matchups, and how they fit into a team comp. With the number of champions that have been added or reworked since then I would basically be starting over to rebuild my understanding of the game.</p>
<p>I'm sure my mechanical skills have taken a hit as well...</p>
<hr />
<p>Over the years I've returned to many other games with similar results.</p>
<p>There's a common thread of temporality that these games share. The time at which you play a game and the time that has elapsed since you last played it both impact the experience.</p>
<p>Even outside of the games themselves changing, for games with a multiplayer element the people you play the game with also impacts the experience. Many of these games I played with friends back in high school and college, and to return to them without the same group of people along with me is probably what impacts my experience the most.</p>
<p>As I've thought about this, I now realize that these experiences with video games are just a stand-in for a deeper truth: there are some things in my life I can never return to in the way I want.</p>
<p>To quote Heraclitus:</p>
<blockquote>
<p>No man ever steps in the same river twice, for it's not the same river and he's not the same man.</p>
</blockquote>
<p>I'm still trying to come to terms with this.</p>
WorkOS Hack Week II2022-07-01T18:52:52.020+00:002022-07-01T18:52:52.020+00:00Unknownhttps://maxdeviant.com/posts/2022/workos-hack-week-ii/<p>This past week was the second Hack Week at WorkOS.</p>
<p>I wanted to take some time to share what I got up to over the course of the week.</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01g6xhwk70yc9p5j8z0bmsm54y" alt="Hack Week 2022 logo by Patrick Marsceill" /></p>
<h2 id="what-is-hack-week">What is Hack Week?</h2>
<p>Hack Week is an event we run somewhat regularly at WorkOS. We take a week where we set aside our usual work and work on other WorkOS-adjacent projects.</p>
<p>For this Hack Week I paired up with Sheldon from our Developer Success team to build out the WorkOS Rust SDK.</p>
<h2 id="rust-sdk-background">Rust SDK Background</h2>
<p>I've wanted to build a Rust SDK for WorkOS for a while now.</p>
<p>My first attempt was back in December 2020, shortly after first joining WorkOS, but I didn't really have bandwidth to work on it at the time. I picked up the project again in April of this year and was able to get the foundations of the project in place. The plan was to find some volunteers to help me finish the SDK on the side.</p>
<p>However, coming into Hack Week we received an inquiry about a Rust SDK from a developer interested in using WorkOS. I'll use whatever excuse I can to write Rust, so I decided that this would be the opportune time to finish up the SDK in preparation for release.</p>
<h2 id="design-principles">Design Principles</h2>
<p>There are a few guiding design principles that we applied while building out the Rust SDK:</p>
<h3 id="make-illegal-states-unrepresentable">Make illegal states unrepresentable</h3>
<p>The phrase <a href="https://twitter.com/yminsky/status/1034947939364425731">"make illegal states unrepresentable"</a> is fairly well-known at this point, but it's still such a powerful principle when designing software interfaces.</p>
<p>In the Rust SDK we do our best to follow this principle and make illegal states unrepresentable, where possible.</p>
<p>A good example of this is when initiating SSO. The "Get Authorization URL" endpoint accepts a parameter known as a "connection selector":</p>
<blockquote>
<p>To indicate the connection to use for authentication, use one of the following connection selectors: <code>connection</code>, <code>organization</code>, or <code>provider</code>.</p>
<p>These connection selectors are mutually exclusive, and exactly one must be provided.</p>
<p>— <a href="https://workos.com/docs/reference/sso/authorize/get">WorkOS Docs: Get Authorization URL</a></p>
</blockquote>
<p>A naive implementation might look something like this:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#89ddff;">#[</span><span>derive</span><span style="color:#89ddff;">(</span><span>Debug</span><span style="color:#89ddff;">)]
</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">struct </span><span>GetAuthorizationUrlParams</span><span style="color:#89ddff;"><</span><span style="color:#c792ea;">'a</span><span style="color:#89ddff;">> {
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// Initiate SSO for the connection with the specified ID.
</span><span> </span><span style="color:#c792ea;">pub </span><span>connection</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">Option</span><span style="color:#89ddff;"><&</span><span style="color:#c792ea;">'a</span><span> ConnectionId</span><span style="color:#89ddff;">></span><span>,
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// Initiate SSO for the organization with the specified ID.
</span><span> </span><span style="color:#c792ea;">pub </span><span>organization</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">Option</span><span style="color:#89ddff;"><&</span><span style="color:#c792ea;">'a</span><span> OrganizationId</span><span style="color:#89ddff;">></span><span>,
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// Initiate SSO for the specified OAuth provider.
</span><span> </span><span style="color:#c792ea;">pub </span><span>provider</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">Option</span><span style="color:#89ddff;"><&</span><span style="color:#c792ea;">'a</span><span> Provider</span><span style="color:#89ddff;">></span><span>,
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>However, this design doesn't capture the mutual exclusivity of the options. We could provide none of them, all of them, or some other invalid combination.</p>
<p>We can remedy this by using Rust's enums, which allow us to model the connection selector as a sum type:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="font-style:italic;color:#4a4a4a;">/// The selector to use to determine which connection to use for SSO.
</span><span style="color:#89ddff;">#[</span><span>derive</span><span style="color:#89ddff;">(</span><span>Debug</span><span style="color:#89ddff;">)]
</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">enum </span><span>ConnectionSelector<'a> </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// Initiate SSO for the connection with the specified ID.
</span><span> Connection</span><span style="color:#89ddff;">(&</span><span style="color:#c792ea;">'a</span><span> ConnectionId</span><span style="color:#89ddff;">),
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// Initiate SSO for the organization with the specified ID.
</span><span> Organization</span><span style="color:#89ddff;">(&</span><span style="color:#c792ea;">'a</span><span> OrganizationId</span><span style="color:#89ddff;">),
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// Initiate SSO for the specified OAuth provider.
</span><span> Provider</span><span style="color:#89ddff;">(&</span><span style="color:#c792ea;">'a</span><span> Provider</span><span style="color:#89ddff;">),
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#89ddff;">#[</span><span>derive</span><span style="color:#89ddff;">(</span><span>Debug</span><span style="color:#89ddff;">)]
</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">struct </span><span>GetAuthorizationUrlParams</span><span style="color:#89ddff;"><</span><span style="color:#c792ea;">'a</span><span style="color:#89ddff;">> {
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// The connection selector to use to initiate SSO.
</span><span> </span><span style="color:#c792ea;">pub </span><span>connection_selector</span><span style="color:#89ddff;">: </span><span>ConnectionSelector</span><span style="color:#89ddff;"><</span><span style="color:#c792ea;">'a</span><span style="color:#89ddff;">></span><span>,
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>The <code>ConnectionSelector</code> type now has the correct semantics: we can only pass one connection selector at a time.</p>
<p>In practice, this would look something like this:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="font-style:italic;color:#c792ea;">let</span><span> authorization_url </span><span style="color:#89ddff;">=</span><span> workos
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">sso</span><span style="color:#89ddff;">()
</span><span> </span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">get_authorization_url</span><span style="color:#89ddff;">(&</span><span>GetAuthorizationUrlParams </span><span style="color:#89ddff;">{
</span><span> client_id</span><span style="color:#89ddff;">: &</span><span>ClientId</span><span style="color:#89ddff;">::</span><span>from</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">client_123456789</span><span style="color:#89ddff;">"),
</span><span> redirect_uri</span><span style="color:#89ddff;">: "</span><span style="color:#c3e88d;">https://your-app.com/callback</span><span style="color:#89ddff;">",
</span><span> connection_selector</span><span style="color:#89ddff;">: </span><span>ConnectionSelector</span><span style="color:#89ddff;">::</span><span>Connection</span><span style="color:#89ddff;">(&</span><span>ConnectionId</span><span style="color:#89ddff;">::</span><span>from</span><span style="color:#89ddff;">(
</span><span> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">conn_01E4ZCR3C56J083X43JQXF3JK5</span><span style="color:#89ddff;">",
</span><span> </span><span style="color:#89ddff;">)),
</span><span> state</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">None</span><span style="color:#89ddff;">,
</span><span> </span><span style="color:#89ddff;">})?;
</span></code></pre>
<h3 id="gracefully-handle-new-enum-variants">Gracefully handle new enum variants</h3>
<p>The WorkOS API returns enums in a number of spots. In statically-typed languages, like Rust, we model the set of possible values within the type system.</p>
<p>Here's what that looks like for a <code>ConnectionType</code>:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#89ddff;">#[</span><span>derive</span><span style="color:#89ddff;">(</span><span>Debug</span><span style="color:#89ddff;">,</span><span> Clone</span><span style="color:#89ddff;">,</span><span> PartialEq</span><span style="color:#89ddff;">,</span><span> Eq</span><span style="color:#89ddff;">,</span><span> Serialize</span><span style="color:#89ddff;">,</span><span> Deserialize</span><span style="color:#89ddff;">)]
</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">enum </span><span>ConnectionType </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// AD FS SAML.
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">///
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// [WorkOS Docs: Integration Guide](https://workos.com/docs/integrations/adfs-saml)
</span><span> </span><span style="color:#89ddff;">#[</span><span>serde</span><span style="color:#89ddff;">(</span><span>rename </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">ADFSSAML</span><span style="color:#89ddff;">")]
</span><span> AdFsSaml</span><span style="color:#89ddff;">,
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// ADP OpenID Connect (OIDC).
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">///
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// [WorkOS Docs: Integration Guide](https://workos.com/docs/integrations/adp-oidc)
</span><span> </span><span style="color:#89ddff;">#[</span><span>serde</span><span style="color:#89ddff;">(</span><span>rename </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">ADPOIDC</span><span style="color:#89ddff;">")]
</span><span> AdpOidc</span><span style="color:#89ddff;">,
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>This comes with the benefit of being able to see exactly which types of connections are available, but unfortunately there is a tradeoff: when new connection types are added within WorkOS upstream (which we do regularly) our <code>ConnectionType</code> enum won't support them.</p>
<p>So what do we do? Make everything <code>String</code>-ly typed?</p>
<p>Thankfully, there's a better solution; one which can give us the benefits of static types while still allowing us to deal with new variants.</p>
<p>Introducing the <code>KnownOrUnknown</code> type:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#89ddff;">#[</span><span>derive</span><span style="color:#89ddff;">(</span><span>Debug</span><span style="color:#89ddff;">,</span><span> Clone</span><span style="color:#89ddff;">,</span><span> PartialEq</span><span style="color:#89ddff;">,</span><span> Eq</span><span style="color:#89ddff;">,</span><span> Serialize</span><span style="color:#89ddff;">,</span><span> Deserialize</span><span style="color:#89ddff;">)]
</span><span style="color:#89ddff;">#[</span><span>serde</span><span style="color:#89ddff;">(</span><span>untagged</span><span style="color:#89ddff;">)]
</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">enum </span><span>KnownOrUnknown<K, U> </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// A known value.
</span><span> Known</span><span style="color:#89ddff;">(</span><span>K</span><span style="color:#89ddff;">),
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// An unknown value.
</span><span> Unknown</span><span style="color:#89ddff;">(</span><span>U</span><span style="color:#89ddff;">),
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>This type allows us to represent a value that is either one of two possible types. For example, with a <code>Connection</code> we can say that the type of the connection is either a <code>ConnectionType</code> that we know about, or some unknown <code>String</code>:</p>
<pre data-lang="rs" style="background-color:#212121;color:#eeffff;" class="language-rs "><code class="language-rs" data-lang="rs"><span style="color:#89ddff;">#[</span><span>derive</span><span style="color:#89ddff;">(</span><span>Debug</span><span style="color:#89ddff;">,</span><span> Clone</span><span style="color:#89ddff;">,</span><span> PartialEq</span><span style="color:#89ddff;">,</span><span> Eq</span><span style="color:#89ddff;">,</span><span> Serialize</span><span style="color:#89ddff;">,</span><span> Deserialize</span><span style="color:#89ddff;">)]
</span><span style="color:#c792ea;">pub </span><span style="font-style:italic;color:#c792ea;">struct </span><span>Connection </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">/// The type of the connection.
</span><span> </span><span style="color:#89ddff;">#[</span><span>serde</span><span style="color:#89ddff;">(</span><span>rename </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">connection_type</span><span style="color:#89ddff;">")]
</span><span> </span><span style="color:#c792ea;">pub</span><span> r#type</span><span style="color:#89ddff;">: </span><span>KnownOrUnknown</span><span style="color:#89ddff;"><</span><span>ConnectionType, </span><span style="color:#ffcb6b;">String</span><span style="color:#89ddff;">></span><span>,
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>If we were to get back <code>"ADFSSAML"</code> from the API, we would end up with a <code>KnownOrUnknown::Known(ConnectionType::AdFsSaml)</code>. Likewise, if got back some new type like <code>"BrandNewType"</code> we would end up with a <code>KnownOrUnknown::Unknown("BrandNewType")</code>.</p>
<h2 id="closing-thoughts">Closing Thoughts</h2>
<p>The first release of the WorkOS Rust SDK is <a href="https://crates.io/crates/workos">now available</a> on crates.io, with the repository being open-sourced soon.</p>
<p>We're still finishing up the final details for a full release, like adding some example applications and code snippets to the WorkOS Docs.</p>
<p>I'm very pleased with how the SDK has turned out and hope that it makes some fellow Rustaceans feel that extra spark of joy while integrating with WorkOS.</p>
<p>This was just one of the many projects that were completed during this Hack Week and I'm sure we'll be sharing more of them soon.</p>
Cohost2022-06-30T03:41:13.134+00:002022-06-30T03:41:13.134+00:00Unknownhttps://maxdeviant.com/shards/2022/cohost/<p><a href="https://cohost.org/rc/welcome">Cohost</a> is bringing back memories of Tumblr before it declined into a <a href="https://maxdeviant.com/shards/2021/tumblr-the-barren-wasteland/">barren wasteland</a>.</p>
<p>I went ahead and created a <a href="https://cohost.org/maxdeviant">profile</a> over there. Despite my recent disillusionment towards platforms in general, I still want to believe that maybe this one will be different.</p>
<p>The principles behind Cohost are certainly ones that I can get behind:</p>
<blockquote>
<p><strong>no ads, no tracking. forever.</strong></p>
<p>cohost will never sell your data, sell ads, or sell the company to anyone who might change these policies to make a quick buck.</p>
</blockquote>
<blockquote>
<p><strong>algorithm? what algorithm?</strong></p>
<p>all your follows’ posts, in the order they were posted, in a timeline that goes vertically. clear and effective moderation done by humans.</p>
</blockquote>
<blockquote>
<p><strong>here’s our whole business model.</strong></p>
<p>for now, you can give us a few bucks a month to help us keep the lights on. soon we’ll let you take tips and sell subscriptions to help you keep the lights on too.</p>
</blockquote>
<p>I'm looking forward to using it (once I get a posting invite link) and seeing how the experience is.</p>
<p>As always, my primary focus will be building out my own platform, on this site and beyond.</p>
<p><img src="https://drop.maxdeviant.com/drops/drop_01g6sab6x8aqgm3new0c0234wp" alt="A banner when you reach the end of your Cohost timeline that reads "There are no posts to read. you're finally free"" /></p>
Reviving Drop Seven and a Half Years Later2022-06-27T03:38:05.337+00:002022-06-27T03:38:05.337+00:00Unknownhttps://maxdeviant.com/posts/2022/reviving-drop-seven-and-a-half-years-later/<p>Back in December 2014 I spent a lot of time hanging out in IRC. While spending time in various channels, I found myself wanting to share images with other users.</p>
<p>Unlike Discord—which came on the scene the following year—IRC did not have the same support for easily sharing images inline; all images needed to be shared as links to somewhere on the internet.</p>
<p>This posed a problem for any image that wasn't sourced directly from a website, such as screenshots or locally-produced images. These would first need to be uploaded to a service, like Imgur, before they could be linked in an IRC channel.</p>
<p>Being a college student on winter break, my solution to this problem was to write my own image sharing service. What resulted was a program called <code>drop</code>.</p>
<h2 id="humble-beginnings">Humble beginnings</h2>
<p><code>drop</code> (stylized as <code>滴</code>) allows you to upload files—primarily images—and get back shareable links to them.</p>
<p>At the time, <code>drop</code> took the form of a small Express app. Files would be uploaded to local disk, saved under their MD5 hash with the original file extension, and could then be referred to by URLs that looked like this:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>http://drop.elliott.codes/i/faff1d9410adf3161446d00fb0c4d107.jpg
</span></code></pre>
<blockquote>
<p>Pardon the <code>http://</code>, Lets Encrypt was only a month into its public release at this point.</p>
</blockquote>
<p>Uploading files was done via a built-in administrative UI protected by a username and password. These credentials were read from the program's configuration as a plaintext username and brypt-hashed password.</p>
<p>There were also some limited analytics built in, with each hit of a file getting recorded in MongoDB.</p>
<p>All of this was deployed to my Linode server with the rest of my projects.</p>
<h2 id="the-death-of-drop">The death of drop</h2>
<p>Over time my usage of <code>drop</code> declined. I was hanging out in Slack and Discord communities instead of IRC, and I also moved away from hosting my own tools.</p>
<p>Unfortunately I don't have a good record of this decline, but the outcome speaks for itself: <code>drop</code> was dead.</p>
<p>But now, after seven and a half years, I decided to revive this project.</p>
<h2 id="revival">Revival</h2>
<p>Over the weekend I rebuilt <code>drop</code> from the ground up, as what I have affectionately (and most originally) named "<code>drop</code> v2".</p>
<p>This time around I decided to use Rust. Underpinning everything is a <a href="https://rocket.rs/">Rocket</a> server, powered by Rocket's brand new support for async Rust.</p>
<p>Rather than being stored on local disk, drops are uploaded to an S3 bucket. Each drop is assigned a <a href="https://github.com/ulid/spec">ULID</a> as its ID. This has some handy benefits, like being able to use the timestamp component to get the time the drop was uploaded.</p>
<p>There's also an administrative SQLite database for managing users and API keys for interacting with the <code>drop</code> API. Interactions with this database are done through the <em>excellent</em> <a href="https://github.com/launchbadge/sqlx"><code>sqlx</code></a> crate.</p>
<blockquote>
<p><code>sqlx</code> allows you to write queries in raw SQL that are then checked at compile-time for syntactic and semantic correctness against the actual database schema.</p>
</blockquote>
<p>The rewrite takes an API-first approach to drop management. For instance we can upload a file like so:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>curl -X POST https://drop.maxdeviant.com/drops \
</span><span> -H "Authorization: Bearer <API_KEY>" \
</span><span> --data-binary "@some-image.png"
</span></code></pre>
<p>We can also upload plain text as a drop:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>echo "A wild drop appeared" | \
</span><span> curl -X POST https://drop.maxdeviant.com/drops \
</span><span> -H "Authorization: Bearer <API_KEY>" \
</span><span> --data-binary @-
</span></code></pre>
<p>Either way, the end result is a URL to a drop:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>https://drop.maxdeviant.com/drops/drop_01g6hevwpk80xyv4e3eqt1dncn
</span></code></pre>
<p>We can <code>curl</code> this drop to see what we get back:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>λ curl -i https://drop.maxdeviant.com/drops/drop_01g6hevwpk80xyv4e3eqt1dncn
</span><span>HTTP/2 200
</span><span>content-type: text/plain
</span><span>server: Fly/9ece5bcd (2022-06-21)
</span><span>x-frame-options: SAMEORIGIN
</span><span>x-content-type-options: nosniff
</span><span>permissions-policy: interest-cohort=()
</span><span>content-length: 21
</span><span>date: Mon, 27 Jun 2022 03:30:17 GMT
</span><span>via: 2 fly.io
</span><span>fly-request-id: 01G6HKBE29WAAPZ71843J47V66-lga
</span><span>
</span><span>A wild drop appeared
</span></code></pre>
<p><code>drop</code> uses the <a href="https://github.com/mbrubeck/tree_magic/"><code>magic_tree_mini</code></a> crate to introspect the drop's binary contents to determine its MIME type. The MIME type is then used to set the <code>Content-Type</code> header on the response, which is what ultimately allows the browser to correctly display the drop.</p>
<blockquote>
<p>This introspection is the same way that the <code>file</code> command works.</p>
</blockquote>
<p>As you might have noticed from the response headers, all of this is running on <a href="https://fly.io/">Fly</a>.</p>
<h2 id="what-s-next">What's next?</h2>
<p>My goal for this weekend was to have a live version of <code>drop</code> up and running again, and that's <a href="https://drop.maxdeviant.com/">exactly what I did</a>. The <a href="https://github.com/maxdeviant/drop">code</a> is a bit of mess and I spent far longer debugging <code>Dockerfile</code>s than I care to admit, but I'm proud to say I shipped the dang thing!</p>
<p>I'm looking forward to start using <code>drop</code> and improving it as I go.</p>
<p>Some things I'd like to look at in the short term:</p>
<ul>
<li>Clean up the code</li>
<li>Add API key scopes to help limit the impact of an API key leaking</li>
<li>Either go <a href="https://fly.io/blog/all-in-on-sqlite-litestream/">all-in on server-side SQLite</a> or switch to Postgres</li>
<li>Figure out where interactions with S3 can be optimized (or even eliminated, through caching)</li>
</ul>
<p>I'm building <code>drop</code> in the open, so have a gander at the <a href="https://github.com/maxdeviant/drop">repo</a> if you'd like to follow along (again, apologies for the current state of the code).</p>
<p>If you want to check out the original code, see the <a href="https://github.com/maxdeviant/drop/tree/v1.0.0"><code>v1.0.0</code> tag</a> on GitHub.</p>
Life Without Limits2022-06-22T03:46:59.038+00:002022-06-22T03:46:59.038+00:00Unknownhttps://maxdeviant.com/shards/2022/life-without-limits/<p>Today I found myself doing some load testing on our API service at work. While the <a href="https://www.artillery.io/">Artillery</a> was shelling the service with traffic, I began thinking about the impact this barrage of requests might have on my home network's egress traffic.</p>
<p>This got me thinking about how my digital life has far too many limits.</p>
<p>I've <a href="https://maxdeviant.com/shards/2021/tumblr-the-barren-wasteland/">written before</a> about my desire for a means to archive content from across the internet. This requires storage space. Archiving textual content is probably manageable, but images will increase the storage requirements, let alone videos.</p>
<p>I could stand to do some preliminary research and napkin math on how much storage I would need in order to support my archival wants each year; will report back when this is done.</p>
<p>Before I become too mired in the realities of it all, the point I want to get across is that limits are <em>tiresome</em>.</p>
<p>After watching <a href="https://en.wikipedia.org/wiki/Limitless_(film)"><em>Limitless</em></a> I became enamored with the idea of NZT-48. I flirted with the thought of going on nootropics, but never ended up doing so, in part because I knew that they couldn't come close in terms of offering the fiction of the drug from the film.</p>
<p>It may sound inane, but I still hold onto some hope that there's an approximation for the power fantasy sold by <em>Limitless</em> to be had in my lifetime. Maybe it's not a pill that you take to unlock your mind, but what if technology could afford you some of those same powers? I dream of a future where you have a digital archival and recall system at your fingertips, able to expand the capacities of your brain as if you were dosing NZT-48 each morning.</p>
Dearth2022-06-21T02:02:23.528+00:002022-06-21T02:02:23.528+00:00Unknownhttps://maxdeviant.com/shards/2022/dearth/<p>There's been a dearth of activity here for a while now.</p>
<p>This really isn't anything all too unusual; there have been plenty of periods in my site's history without any updates. However, this time I more deeply feel the empty space.</p>
<p>A gaping void cries out to be filled.</p>
<p>Part of the reason for this dearth is my being in consumer mode for the past while. I feel like a bear in the course of its hibernation: consuming an abundance of everything and then holing up in a deep, dark den to wait out the winter's cold.</p>
<p>But as surely as spring arrives and the bear wakes from its slumber, so too can I feel myself awakening, ready to create something anew.</p>
Church Encoding and Legos2021-11-14T00:00:00+00:002021-11-14T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0017-church-encoding-and-legos/<p>Greeting travelers.</p>
<p>The weather has been ever-so-dreary here this week. This weekend, in particular, has really been exuding gloomy November vibes. SAD season is truly here.</p>
<p>This has left me with plenty of time to sit inside, drink tea, hack on various things, and ponder some of the finer things in life (like Church encoding).</p>
<h2 id="advent-of-code">Advent of Code</h2>
<p><a href="https://adventofcode.com/">Advent of Code</a> is less than a month away, so this week I did a bit of prep for that. Thus far I have yet to attain all fifty stars for any year. This year I'd like to change that, but I tend to say that every year. There's always a chance that this year will be different!</p>
<p>I'll be using PureScript again this year. Half the fun of doing Advent of Code for me is being able to do it in a functional language, and I am still on a pretty big PureScript kick these days. PureScript is quite nice, both in terms of the language itself and the tooling surrounding it.</p>
<p>This year I am trying something slightly different by doing all of the puzzles within a single project. Previously I would set up a separate project for each day, which came with a lot of overhead. I took this setup out for the test drive on the 2017 puzzles and so far it feels really good.</p>
<p>You can check out my <a href="https://github.com/maxdeviant/advent-of-code/tree/master/2021/purescript">repo</a> to see the setup I'll be using going in.</p>
<h2 id="programming-with-something">Programming with Something</h2>
<p>I watched Tom Stuart's <a href="https://tomstu.art/programming-with-something">"Programming with Something"</a> talk and would highly recommend it. I went in not really knowing what to expect (having not seen his prior talk, "Programming with Nothing"), but I walked away having learned a lot.</p>
<p>Back in college we covered the lambda calculus in my <em>Programming Concepts and Paradigms</em> course. I say "covered", but I suppose it would be more accurately described as "taught to us in the very last class before the final with the express purpose of being put on the final". Needless to say, the concept didn't really click with me at the time. However, while watching Tom's talk I could feel those seeds planted back then start to sprout, and suddenly it all made sense!</p>
<h2 id="programs-as-lego-structures">Programs as Lego Structures</h2>
<p>I did some reminiscing on a childhood spend playing with Legos and how it intersects with my love of functional programming today. Read more about how I think about <a href="https://maxdeviant.com/shards/2021/programs-as-lego-structures/">"Programs as Lego Structures"</a>.</p>
<hr />
<p>That's all for this week!</p>
<p>Until the next one,</p>
<p>Marshall</p>
Programs as Lego Structures2021-11-11T05:04:17.607+00:002021-11-11T05:04:17.607+00:00Unknownhttps://maxdeviant.com/shards/2021/programs-as-lego-structures/<p>As a child, I was very much a <a href="https://en.wikipedia.org/wiki/Lego">Lego</a> enthusiast. My brother and I had these massive tubs filled with Lego bricks and minifigs. Much to our mother's dismay, we would dump everything out on the floor of our bedroom and spread the pieces out. After all, it's much easier to find that exact piece you're hunting for with everything in sight. The alternative, of course, being rummaging blindly through a thirty gallon storage bin.</p>
<p>When you spend as much time as I did working on new builds, you start to gain a familiarity with the pieces you have at your disposal. While working on a structure, I'd realize that I needed <em>that one piece</em> to get the outcome I desired.</p>
<hr />
<p>Fast-forward to now and I still find myself in a similar situation to those days spent sifting through Lego bricks on my bedroom floor. Only now instead of building castles I'm building computer programs.</p>
<p>I prefer to write my code in a functional style, which, if you squint a little, bears a striking resemblance to working with Legos.</p>
<p>In functional programming, <strong>functions are your bricks</strong>.</p>
<p>Just as each brick has its own shape, so do functions have their own shape. Likewise, just as Lego bricks lock together to form a cohesive structure, so too can you compose functions together to form a complete program.</p>
<p>As you write programs in this style, you start to see where certain "bricks" fit in. For instance, when writing a PureScript program I may find myself faced with the following hole:</p>
<pre data-lang="purs" style="background-color:#212121;color:#eeffff;" class="language-purs "><code class="language-purs" data-lang="purs"><span style="color:#89ddff;">? :: </span><span style="color:#c792ea;">forall</span><span> a</span><span style="color:#89ddff;">. </span><span>Array (Maybe a) </span><span style="color:#89ddff;">-> </span><span>Array a
</span></code></pre>
<p>What function would slot in here?</p>
<p>Having seen this shape before enough times, I know that the function needed is <code>Array.catMaybes</code>.</p>
<p>But suppose you didn't know that offhand. How would you find the function that fits this slot? If we were still talking about Legos, you would have to go panning through bins to find the right piece. If you were desperate, you might event head over to the <a href="https://www.lego.com/en-us/page/static/pick-a-brick">Pick a Brick</a> page on the Lego website to search for (and buy) the piece that you need.</p>
<p>Conveniently enough, we can do the same thing here. Plugging the function signature into <a href="https://pursuit.purescript.org/search?q=forall+a.+Array+(Maybe+a)+-%3E+Array+a">Pursuit's search</a> will reveal that <code>Array.catMaybes</code> is the function we need.</p>
<blockquote>
<p>If we were writing Haskell, <a href="https://hoogle.haskell.org/">Hoogle</a> can be used in the same fashion.</p>
</blockquote>
<p>Programming in this fashion can be incredibly powerful. Do it for long enough and you'll start to develop an intution for when the function you need already exists<sup class="footnote-reference"><a href="#1">1</a></sup>, all you need to do is find it.</p>
<hr />
<p>An interesting property of Legos is this:</p>
<blockquote>
<p>you never make your own Lego bricks.</p>
</blockquote>
<p>Let that sink in.</p>
<p><strong>You never make your own Lego bricks</strong>, yet you can still build all sorts of fantastic creations.</p>
<p>The reason for this is rather simple: Lego provides incredibly good primitives for you to use. These building blocks<sup class="footnote-reference"><a href="#2">2</a></sup> provide enough power and variety to assemble them in all sorts of ways to fit any situation you can think of.</p>
<p>The functional paradigm has a lot of excellent primitives. The concepts pulled from category theory—functors, semigroups, applicatives, you name it—are all highly general and widely applicable.</p>
<p>It makes me wonder: what sort of programs can be built given enough good primitives?</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p><code>traverse</code>, it's always <code>traverse</code>.</p>
</div>
<!-- -->
<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup>
<p>No pun intended.</p>
</div>
Hello Again2021-11-09T00:00:00+00:002021-11-09T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0016-hello-again/<p>Hello again, travelers.</p>
<p>It has been too long since I last I wrote to you. A year and a day without any missives.</p>
<p>I have been keeping busy over the past year, mostly with work. I just recently <a href="https://twitter.com/maxdeviant/status/1455998714800185348">celebrated</a> my one year anniversary at WorkOS, despite it feeling like I only joined the company very recently.</p>
<p>Outside of work, I've recently been hacking on two new side projects:</p>
<ul>
<li><a href="https://github.com/maxdeviant/peregrine">Peregrine</a> is a web framework for PureScript. My goal is to build a fast, pleasant framework that lives at the intersection of web fundamentals and FP sensibilities.</li>
<li><a href="https://github.com/maxdeviant/ravenwm">ravenwm</a> is my foray into the world of Linux window managers, using Rust. I've been having a lot of fun with it so far and have enjoyed stepping out of my usual comfort zone.</li>
</ul>
<p>In addition to these personal projects I have also been ramping back up on my contributions in the PureScript ecosystem, namely helping out with the new PureScript registry.</p>
<hr />
<p>The leaves are starting to turn here and the sun is setting far earlier than I would like. It is definitely feeling like a good time to hole up indoors, even if that isn't much of a departure from the year and a half or so.</p>
<p>I hope to get back into the swing of keeping on top of this newsletter, so expect to hear from me again soon.</p>
<p>Until next time,</p>
<p>Marshall</p>
Formatting Numbers: Revisited2021-08-28T02:21:09.379+00:002021-08-28T02:21:09.379+00:00Unknownhttps://maxdeviant.com/shards/2021/formatting-numbers-revisited/<p>A few months ago I asked <a href="https://maxdeviant.com/shards/2021/how-hard-is-it-to-format-a-number/">"how hard is it to format a number?"</a>. The solution I came up with at the time was to use some light JavaScript to format the numbers using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString"><code>Number.prototype.toLocaleString()</code></a>.</p>
<p>Of course, this was not good enough for me.</p>
<p>Ideally, I would like to use a <a href="https://tera.netlify.app/docs/#filters">Tera filter</a> to format the numbers at site generation time:</p>
<pre data-lang="jinja2" style="background-color:#212121;color:#eeffff;" class="language-jinja2 "><code class="language-jinja2" data-lang="jinja2"><span>{% </span><span style="font-style:italic;color:#c792ea;">set </span><span>word_count </span><span style="color:#89ddff;">=</span><span> 9001 %}
</span><span>{{ word_count </span><span style="color:#89ddff;">| </span><span>num_format }}
</span><span><!-- 9,001 -->
</span></code></pre>
<p>This is incredibly reminiscent of how formatting dates works with <a href="https://tera.netlify.app/docs/#date">Tera's <code>date</code> filter</a>.</p>
<p>I ended up <a href="https://github.com/getzola/zola/pull/1460">adding</a> a <a href="https://www.getzola.org/documentation/templates/overview/#num-format"><code>num_format</code> filter</a> to Zola, powered by the excellent <a href="https://crates.io/crates/num-format">crate of the same name</a>.</p>
<p>As I write this, I have since updated my <a href="https://maxdeviant.com/stats/">stats</a> page to take advantage of this new filter. Inspect the source and you'll see that the numbers are nicely pre-formatted in the HTML—no JavaScript required!</p>
Another Nix Success Story2021-08-05T19:51:22.725+00:002021-08-05T19:51:22.725+00:00Unknownhttps://maxdeviant.com/shards/2021/another-nix-success-story/<p>Yesterday I found myself in a position where I needed to know the dependency graph of tables within a Postgres database.</p>
<p>I figured that someone else had written a program to solve this problem, and some quick Google-fu revealed that I was correct.</p>
<p>I happened upon <a href="https://sigterm.sh/2010/07/09/generating-a-dependency-graph-for-a-postgresql-database/">this post</a> that contained a Python script that generated the dependency graph I was looking for and dumped it to Graphviz <a href="https://en.wikipedia.org/wiki/DOT_(graph_description_language)">DOT</a>. Beautiful!</p>
<p>Here's the script:</p>
<pre data-lang="py" style="background-color:#212121;color:#eeffff;" class="language-py "><code class="language-py" data-lang="py"><span style="font-style:italic;color:#c792ea;">from </span><span>optparse </span><span style="font-style:italic;color:#c792ea;">import </span><span>OptionParser</span><span style="color:#89ddff;">, </span><span>OptionGroup
</span><span>
</span><span style="font-style:italic;color:#c792ea;">import </span><span>psycopg2
</span><span style="font-style:italic;color:#c792ea;">import </span><span>sys
</span><span>
</span><span>
</span><span style="font-style:italic;color:#c792ea;">def </span><span style="color:#82aaff;">writedeps</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">cursor</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">tbl</span><span style="color:#89ddff;">):
</span><span> sql </span><span style="color:#89ddff;">= """</span><span style="color:#c792ea;">SELECT
</span><span style="color:#c3e88d;"> tc.constraint_name, tc.table_name, kcu.column_name,
</span><span style="color:#c3e88d;"> ccu.table_name </span><span style="color:#89ddff;">AS</span><span style="color:#c3e88d;"> foreign_table_name,
</span><span style="color:#c3e88d;"> ccu.column_name </span><span style="color:#89ddff;">AS</span><span style="color:#c3e88d;"> foreign_column_name
</span><span style="color:#c3e88d;"> </span><span style="color:#c792ea;">FROM
</span><span style="color:#c3e88d;"> information_schema.table_constraints </span><span style="color:#89ddff;">AS</span><span style="color:#c3e88d;"> tc
</span><span style="color:#c3e88d;"> </span><span style="color:#c792ea;">JOIN </span><span style="color:#c3e88d;">information_schema.key_column_usage </span><span style="color:#89ddff;">AS</span><span style="color:#c3e88d;"> kcu ON
</span><span style="color:#c3e88d;"> tc.constraint_name </span><span style="color:#89ddff;">= </span><span style="color:#c3e88d;">kcu.constraint_name
</span><span style="color:#c3e88d;"> </span><span style="color:#c792ea;">JOIN </span><span style="color:#c3e88d;">information_schema.constraint_column_usage </span><span style="color:#89ddff;">AS</span><span style="color:#c3e88d;"> ccu ON
</span><span style="color:#c3e88d;"> ccu.constraint_name </span><span style="color:#89ddff;">= </span><span style="color:#c3e88d;">tc.constraint_name
</span><span style="color:#c3e88d;"> </span><span style="color:#c792ea;">WHERE</span><span style="color:#c3e88d;"> constraint_type </span><span style="color:#89ddff;">= '</span><span style="color:#c3e88d;">FOREIGN KEY</span><span style="color:#89ddff;">' AND </span><span style="color:#c3e88d;">tc.table_name </span><span style="color:#89ddff;">= '</span><span>%s</span><span style="color:#89ddff;">'"""
</span><span> </span><span style="color:#82aaff;">cursor</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">execute</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">sql </span><span style="color:#89ddff;">% </span><span style="color:#82aaff;">tbl</span><span style="color:#89ddff;">)
</span><span> </span><span style="font-style:italic;color:#c792ea;">for </span><span>row </span><span style="font-style:italic;color:#c792ea;">in </span><span style="color:#82aaff;">cursor</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">fetchall</span><span style="color:#89ddff;">():
</span><span> constraint, table, column, foreign_table, foreign_column </span><span style="color:#89ddff;">= </span><span>row
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">"</span><span>%s</span><span style="color:#c3e88d;">" -> "</span><span>%s</span><span style="color:#c3e88d;">" [label="</span><span>%s</span><span style="color:#c3e88d;">"];</span><span style="color:#89ddff;">' % (</span><span>tbl</span><span style="color:#89ddff;">, </span><span>foreign_table</span><span style="color:#89ddff;">, </span><span>constraint</span><span style="color:#89ddff;">)
</span><span>
</span><span>
</span><span style="font-style:italic;color:#c792ea;">def </span><span style="color:#82aaff;">get_tables</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">cursor</span><span style="color:#89ddff;">):
</span><span> </span><span style="color:#82aaff;">cursor</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">execute</span><span style="color:#89ddff;">("</span><span style="color:#c792ea;">SELECT</span><span style="color:#c3e88d;"> tablename </span><span style="color:#c792ea;">FROM</span><span style="color:#c3e88d;"> pg_tables </span><span style="color:#c792ea;">WHERE</span><span style="color:#c3e88d;"> schemaname</span><span style="color:#89ddff;">='</span><span style="color:#c3e88d;">public</span><span style="color:#89ddff;">'")
</span><span> </span><span style="font-style:italic;color:#c792ea;">for </span><span>row </span><span style="font-style:italic;color:#c792ea;">in </span><span style="color:#82aaff;">cursor</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">fetchall</span><span style="color:#89ddff;">():
</span><span> </span><span style="font-style:italic;color:#c792ea;">yield </span><span>row</span><span style="color:#89ddff;">[</span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">]
</span><span>
</span><span>
</span><span style="font-style:italic;color:#c792ea;">def </span><span style="color:#82aaff;">main</span><span style="color:#89ddff;">():
</span><span> parser </span><span style="color:#89ddff;">= </span><span style="color:#82aaff;">OptionParser</span><span style="color:#89ddff;">()
</span><span>
</span><span> group </span><span style="color:#89ddff;">= </span><span style="color:#82aaff;">OptionGroup</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">parser</span><span style="color:#89ddff;">, "</span><span style="color:#c3e88d;">Database Options</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#82aaff;">group</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">add_option</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">--dbname</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">action</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">store</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">dest</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">dbname</span><span style="color:#89ddff;">",
</span><span style="color:#82aaff;"> </span><span style="color:#f78c6c;">help</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">The database name.</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#82aaff;">group</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">add_option</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">--dbhost</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">action</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">store</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">dest</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">dbhost</span><span style="color:#89ddff;">",
</span><span style="color:#82aaff;"> </span><span style="color:#f78c6c;">default</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">localhost</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">help</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">The database host.</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#82aaff;">group</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">add_option</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">--dbuser</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">action</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">store</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">dest</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">dbuser</span><span style="color:#89ddff;">",
</span><span style="color:#82aaff;"> </span><span style="color:#f78c6c;">help</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">The database username.</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#82aaff;">group</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">add_option</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">--dbpass</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">action</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">store</span><span style="color:#89ddff;">", </span><span style="color:#f78c6c;">dest</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">dbpass</span><span style="color:#89ddff;">",
</span><span style="color:#82aaff;"> </span><span style="color:#f78c6c;">help</span><span style="color:#89ddff;">="</span><span style="color:#c3e88d;">The database password.</span><span style="color:#89ddff;">")
</span><span> </span><span style="color:#82aaff;">parser</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">add_option_group</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">group</span><span style="color:#89ddff;">)
</span><span>
</span><span> </span><span style="color:#89ddff;">(</span><span>options</span><span style="color:#89ddff;">, </span><span>args</span><span style="color:#89ddff;">) = </span><span style="color:#82aaff;">parser</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">parse_args</span><span style="color:#89ddff;">()
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span style="color:#89ddff;">not </span><span>options</span><span style="color:#89ddff;">.</span><span>dbname</span><span style="color:#89ddff;">:
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Please supply a database name, see --help for more info.</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#82aaff;">sys</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">exit</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">)
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">try</span><span style="color:#89ddff;">:
</span><span> conn </span><span style="color:#89ddff;">= </span><span style="color:#82aaff;">psycopg2</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">connect</span><span style="color:#89ddff;">("</span><span style="color:#c3e88d;">dbname='</span><span>%s</span><span style="color:#c3e88d;">' user='</span><span>%s</span><span style="color:#c3e88d;">' host='</span><span>%s</span><span style="color:#c3e88d;">' password='</span><span>%s</span><span style="color:#c3e88d;">'</span><span style="color:#89ddff;">"
</span><span style="color:#82aaff;"> </span><span style="color:#89ddff;">% (</span><span style="color:#82aaff;">options</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">dbname</span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">options</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">dbuser</span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">options</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">dbhost</span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">options</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">dbpass</span><span style="color:#89ddff;">))
</span><span> </span><span style="font-style:italic;color:#c792ea;">except </span><span>psycopg2.OperationalError</span><span style="color:#89ddff;">, </span><span>e</span><span style="color:#89ddff;">:
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Failed to connect to database,</span><span style="color:#89ddff;">"</span><span>,
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">perhaps you need to supply auth details:</span><span style="color:#89ddff;">\n </span><span>%s</span><span style="color:#89ddff;">" % </span><span style="color:#ffcb6b;">str</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">e</span><span style="color:#89ddff;">)
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Use --help for more info.</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#82aaff;">sys</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">exit</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">)
</span><span>
</span><span> cursor </span><span style="color:#89ddff;">= </span><span style="color:#82aaff;">conn</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">cursor</span><span style="color:#89ddff;">()
</span><span>
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Digraph F {</span><span style="color:#89ddff;">\n"
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">ranksep=1.0; size="18.5, 15.5"; rankdir=LR;</span><span style="color:#89ddff;">'
</span><span> </span><span style="font-style:italic;color:#c792ea;">for </span><span>i </span><span style="font-style:italic;color:#c792ea;">in </span><span style="color:#82aaff;">get_tables</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">cursor</span><span style="color:#89ddff;">):
</span><span> </span><span style="color:#82aaff;">writedeps</span><span style="color:#89ddff;">(</span><span style="color:#82aaff;">cursor</span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">i</span><span style="color:#89ddff;">)
</span><span> </span><span style="color:#c792ea;">print </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">}</span><span style="color:#89ddff;">"
</span><span>
</span><span> </span><span style="color:#82aaff;">sys</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">exit</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">)
</span><span>
</span><span>
</span><span style="font-style:italic;color:#c792ea;">if </span><span>__name__ </span><span style="color:#89ddff;">== "</span><span style="color:#c3e88d;">__main__</span><span style="color:#89ddff;">":
</span><span> </span><span style="color:#82aaff;">main</span><span style="color:#89ddff;">()
</span></code></pre>
<p>It was herein that I discovered two problems:</p>
<ol>
<li>The script depends on <code>psycopg2</code>, which means I need to muck about with Python dependency management</li>
<li>I can tell from the <code>print</code> statements that the script is written in Python 2, and my work MacBook has Python 3</li>
</ol>
<p>My immediate thought was to use Nix to solve this. I grabbed a <code>shell.nix</code> template from the <a href="https://nixos.wiki/wiki/Packaging/Python#Pip_and_Virtualenv_enabled_nix-shell"><code>Packaging/Python</code></a> section of the NixOS wiki and added the <code>psycopg2</code> dependency along with <code>graphviz</code>:</p>
<pre data-lang="diff" style="background-color:#212121;color:#eeffff;" class="language-diff "><code class="language-diff" data-lang="diff"><span> with import <nixpkgs> {};
</span><span>
</span><span> stdenv.mkDerivation {
</span><span> name = "pip-env";
</span><span> buildInputs = [
</span><span style="color:#89ddff;">+</span><span style="color:#c3e88d;"> graphviz
</span><span style="color:#89ddff;">+
</span><span> # System requirements.
</span><span> readline
</span><span>
</span><span> # Python requirements (enough to get a virtualenv going).
</span><span> python27Full
</span><span> python27Packages.virtualenv
</span><span> python27Packages.pip
</span><span style="color:#89ddff;">+</span><span style="color:#c3e88d;"> python27Packages.psycopg2
</span><span> ];
</span><span> src = null;
</span><span> shellHook = ''
</span><span> # Allow the use of wheels.
</span><span> SOURCE_DATE_EPOCH=$(date +%s)
</span><span>
</span><span> # Augment the dynamic linker path
</span><span> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${R}/lib/R/lib:$ {readline}/lib
</span><span> '';
</span><span> }
</span></code></pre>
<p>I was then able to drop into a <code>nix-shell</code> and run the script:</p>
<pre data-lang="sh" style="background-color:#212121;color:#eeffff;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#82aaff;">python database_dependency_order.py</span><span style="color:#89ddff;"> --</span><span style="color:#f78c6c;">dbname </span><span style="color:#89ddff;"><</span><span style="color:#82aaff;">NAME</span><span style="color:#89ddff;">> -</span><span style="color:#82aaff;">-dbhost </span><span style="color:#89ddff;"><</span><span style="color:#82aaff;">HOST</span><span style="color:#89ddff;">> -</span><span style="color:#82aaff;">-dbuser </span><span style="color:#89ddff;"><</span><span style="color:#82aaff;">USER</span><span style="color:#89ddff;">> -</span><span style="color:#82aaff;">-dbpass </span><span style="color:#89ddff;"><</span><span style="color:#82aaff;">PASSWORD</span><span style="color:#89ddff;">> | </span><span style="color:#82aaff;">dot</span><span style="color:#89ddff;"> -</span><span style="color:#f78c6c;">Tpng </span><span style="color:#89ddff;">></span><span style="color:#82aaff;"> deps.png
</span></code></pre>
<p>Easy as <del>Py</del> Nix.</p>
React Associated Components2021-07-10T21:50:46.986+00:002021-07-10T21:50:46.986+00:00Unknownhttps://maxdeviant.com/posts/2021/react-associated-components/<p>When building React components I often find myself in situations where there are groups of components that regularly appear together. For example, consider this <code>Card</code> component that can be used with header, body, and footer components:</p>
<pre data-lang="tsx" style="background-color:#212121;color:#eeffff;" class="language-tsx "><code class="language-tsx" data-lang="tsx"><span style="font-style:italic;color:#c792ea;">import </span><span>React </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">react</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>Card </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./Card</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>CardBody </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./CardBody</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>CardFooter </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./CardFooter</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>CardHeader </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./CardHeader</span><span style="color:#89ddff;">';
</span><span>
</span><span style="font-style:italic;color:#c792ea;">const </span><span style="color:#82aaff;">App </span><span style="color:#89ddff;">= () </span><span style="font-style:italic;color:#c792ea;">=> </span><span>(
</span><span> </span><span style="color:#89ddff;"><</span><span style="color:#ffcb6b;">Card</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">CardHeader</span><span style="color:#89ddff;">>A Movie Listing (2021)</</span><span style="color:#ffcb6b;">CardHeader</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">CardBody</span><span style="color:#89ddff;">>This movie is awesome!</</span><span style="color:#ffcb6b;">CardBody</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">CardFooter</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">Button</span><span style="color:#89ddff;">>Watch Movie</</span><span style="color:#ffcb6b;">Button</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> </</span><span style="color:#ffcb6b;">CardFooter</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> </</span><span style="color:#ffcb6b;">Card</span><span style="color:#89ddff;">>
</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<p>It can be rather cumbersome to have to import all of the various components every time we want to use them, especially since every time we use a <code>Card</code> we'll want to use one or more of these components with it. Likewise, these components won't be used outside the context of a <code>Card</code>.</p>
<p>Instead, we can make <code>CardHeader</code>, <code>CardBody</code>, and <code>CardFooter</code> associated components<sup class="footnote-reference"><a href="#1">1</a></sup> on the <code>Card</code>:</p>
<pre data-lang="tsx" style="background-color:#212121;color:#eeffff;" class="language-tsx "><code class="language-tsx" data-lang="tsx"><span style="font-style:italic;color:#c792ea;">import </span><span>React </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">react</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>Card </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./Card</span><span style="color:#89ddff;">';
</span><span>
</span><span style="font-style:italic;color:#c792ea;">const </span><span style="color:#82aaff;">App </span><span style="color:#89ddff;">= () </span><span style="font-style:italic;color:#c792ea;">=> </span><span>(
</span><span> </span><span style="color:#89ddff;"><</span><span style="color:#ffcb6b;">Card</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">Card.Header</span><span style="color:#89ddff;">>A Movie Listing (2021)</</span><span style="color:#ffcb6b;">Card.Header</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">Card.Body</span><span style="color:#89ddff;">>This movie is awesome!</</span><span style="color:#ffcb6b;">Card.Body</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">Card.Footer</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> <</span><span style="color:#ffcb6b;">Button</span><span style="color:#89ddff;">>Watch Movie</</span><span style="color:#ffcb6b;">Button</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> </</span><span style="color:#ffcb6b;">Card.Footer</span><span style="color:#89ddff;">>
</span><span style="color:#89ddff;"> </</span><span style="color:#ffcb6b;">Card</span><span style="color:#89ddff;">>
</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<p>Here's what things look like under the hood:</p>
<pre data-lang="tsx" style="background-color:#212121;color:#eeffff;" class="language-tsx "><code class="language-tsx" data-lang="tsx"><span style="font-style:italic;color:#c792ea;">import </span><span>React </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">react</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>CardBody </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./CardBody</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>CardFooter </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./CardFooter</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>CardHeader </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">./CardHeader</span><span style="color:#89ddff;">';
</span><span>
</span><span style="font-style:italic;color:#c792ea;">export interface </span><span>CardProps
</span><span> </span><span style="color:#c792ea;">extends </span><span>React</span><span style="color:#89ddff;">.</span><span style="color:#c3e88d;">DetailedHTMLProps</span><span style="color:#89ddff;"><
</span><span> React</span><span style="color:#89ddff;">.</span><span>HTMLAttributes</span><span style="color:#89ddff;"><</span><span>HTMLDivElement</span><span style="color:#89ddff;">>,
</span><span> HTMLDivElement
</span><span> </span><span style="color:#89ddff;">> {}
</span><span>
</span><span style="font-style:italic;color:#c792ea;">export interface </span><span>Card </span><span style="color:#89ddff;">{
</span><span> Header</span><span style="color:#89ddff;">: typeof </span><span>CardHeader</span><span style="color:#89ddff;">;
</span><span> Body</span><span style="color:#89ddff;">: typeof </span><span>CardBody</span><span style="color:#89ddff;">;
</span><span> Footer</span><span style="color:#89ddff;">: typeof </span><span>CardFooter</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="font-style:italic;color:#c792ea;">export const </span><span>Card</span><span style="color:#89ddff;">: </span><span>React</span><span style="color:#89ddff;">.</span><span>FC</span><span style="color:#89ddff;"><</span><span>Readonly</span><span style="color:#89ddff;"><</span><span>CardProps</span><span style="color:#89ddff;">>> & </span><span>Card </span><span style="color:#89ddff;">= </span><span>(</span><span style="color:#89ddff;">{
</span><span> children</span><span style="color:#89ddff;">,
</span><span> </span><span style="color:#89ddff;">...</span><span>props
</span><span style="color:#89ddff;">}</span><span>) </span><span style="font-style:italic;color:#c792ea;">=> </span><span>(
</span><span style="color:#89ddff;"> </span><span style="font-style:italic;color:#4a4a4a;">// The most basic of `Card`s.
</span><span> </span><span style="color:#89ddff;"><</span><span style="color:#f07178;">div </span><span style="color:#89ddff;">{...</span><span>props</span><span style="color:#89ddff;">}>{</span><span>children</span><span style="color:#89ddff;">}</</span><span style="color:#f07178;">div</span><span style="color:#89ddff;">>
</span><span>)</span><span style="color:#89ddff;">;
</span><span>
</span><span>Card</span><span style="color:#89ddff;">.</span><span>Header </span><span style="color:#89ddff;">= </span><span>CardHeader</span><span style="color:#89ddff;">;
</span><span>Card</span><span style="color:#89ddff;">.</span><span>Body </span><span style="color:#89ddff;">= </span><span>CardBody</span><span style="color:#89ddff;">;
</span><span>Card</span><span style="color:#89ddff;">.</span><span>Footer </span><span style="color:#89ddff;">= </span><span>CardFooter</span><span style="color:#89ddff;">;
</span></code></pre>
<p>And that's all there is to it!</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>This name is inspired by Rust's <a href="https://doc.rust-lang.org/rust-by-example/generics/assoc_items/types.html">associated types</a>.</p>
</div>
How Hard Is It to Format a Number?2021-05-16T04:34:13.303+00:002021-05-16T04:34:13.303+00:00Unknownhttps://maxdeviant.com/shards/2021/how-hard-is-it-to-format-a-number/<p>The answer, as it would appear, is "very".</p>
<p>Tonight I spun up a new <a href="https://maxdeviant.com/stats/">stats</a> page for my site. Since I already have word counts for all of the writing I post here, I wanted to sum them up to know how many words I've written in total.</p>
<p>Some quick and dirty hacking in <a href="https://tera.netlify.app/">Tera</a> produced the numbers I needed, which then presented a problem: how do I format a number with comma separators?</p>
<p>My first instinct was to hunt for a Tera filter to do this. After all, this seemed very similar to <a href="https://tera.netlify.app/docs/#date">date formatting</a>. Scouring the Tera docs and GitHub issues left me empty-handed; apparently I'm the first person who wants to dynamically format numbers in a static site generator.</p>
<p>At this point I took a detour that revolved around me trying to remember high school math as I tried to implement a number formatting algorithm using Tera's macro system. It didn't take long before I labeled this a fool's errand and decided to give up.</p>
<p>In the end I resorted to using JavaScript's <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString"><code>toLocaleString()</code></a> to solve this issue:</p>
<pre data-lang="js" style="background-color:#212121;color:#eeffff;" class="language-js "><code class="language-js" data-lang="js"><span>(</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>elements </span><span style="color:#89ddff;">= </span><span style="color:#82aaff;">document</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">getElementsByClassName</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">locale-num</span><span style="color:#89ddff;">'</span><span>)</span><span style="color:#89ddff;">;
</span><span> </span><span style="font-style:italic;color:#c792ea;">for </span><span>(</span><span style="font-style:italic;color:#c792ea;">let </span><span>i </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">; </span><span>i </span><span style="color:#89ddff;">< </span><span>elements</span><span style="color:#89ddff;">.</span><span>length</span><span style="color:#89ddff;">; </span><span>i</span><span style="color:#89ddff;">++</span><span>) </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>n </span><span style="color:#89ddff;">= </span><span style="color:#82aaff;">parseInt</span><span>(elements[i]</span><span style="color:#89ddff;">.</span><span>textContent)</span><span style="color:#89ddff;">;
</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span>(</span><span style="color:#89ddff;">!</span><span style="color:#82aaff;">isNaN</span><span>(n)) </span><span style="color:#89ddff;">{
</span><span> elements[i]</span><span style="color:#89ddff;">.</span><span>textContent </span><span style="color:#89ddff;">= </span><span>n</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">toLocaleString</span><span>()</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}
</span><span> </span><span style="color:#89ddff;">}
</span><span style="color:#89ddff;">}</span><span>)()</span><span style="color:#89ddff;">;
</span></code></pre>
<p>My only consolation is that this JavaScript snippet is the very definition of "progressive enhancement". If someone were to visit the page and not have JavaScript enabled, the worst that could happen is they have to squint a little harder to read some numbers.</p>
Tumblr: The Barren Wasteland2021-05-10T03:04:11.615+00:002021-05-10T03:04:11.615+00:00Unknownhttps://maxdeviant.com/shards/2021/tumblr-the-barren-wasteland/<p>When I think back to the glory days of Tumblr I can't help but feel a profound sense of loss over what has become of it. The site I once loved and built a home on is now nothing more than a barren wasteland.</p>
<p>My most heavy usage of Tumblr was during my time in college, from 2012-2016. Scrolling back through my archive, I'm greeted with a smattering of reblogs of all manner of content. Quotes, moodboard-worthy photos and illustrations, GIFs from the TV shows I was watching at the time—it's all there.</p>
<p>Using Tumblr felt infinite. It felt like the content I reblogged would last forever, like a permanent addition to my brain. Ever since then, I've found myself chasing that feeling I got using Tumblr. I want a place where I can capture tiny snapshots of time and immortalize them in a mosaic spanning a lifetime.</p>
<p>My appetite has only grown over time. I now find myself yearning for a way to archive content from across the internet. Far too often I'll find once-saved links are now dead. URLs changed, hosting providers switched, sites and their content forever lost to time. Every time I come across a dead link, a piece of me dies with it.</p>
<p>What would it take to build a system that could capture a lifetime of human memories? <em>Black Mirror</em>'s "The Entire History of You" explores why this might not be a good idea, but I still can't shake my desire for it. The risk presented by being able to relive an unpleasant memory over and over again is nothing compared to the utter despondence of knowing a memory exists but being unable to access it.</p>
<p>Last October I was thinking back to my adolescent years in China. I ended up trawling Baidu Maps' street view trying to catch glimpses of my childhood. While most of the locations were still mostly intact, they were a far cry from how I remember them more than ten years ago. Many of the street view images were even a number of years old, meaning their current state might be totally unrecognizable.</p>
<p>Even during this exercise, I found it difficult to find my way around streets that I used to travel often. Some of this is no doubt due to not being in the physical space, but I fear that some of it is just my memories of these ventures being locked somewhere deep inside of me. If only there was a reliable way to recall them.</p>
Optional Stacking in TypeScript2021-05-10T00:11:29.031+00:002021-05-10T00:11:29.031+00:00Unknownhttps://maxdeviant.com/posts/2021/optional-stacking-in-typescript/<p>Nullable references—commonly referred to as the <a href="https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/">billion dollar mistake</a>—are a familiar sight in many programming languages.</p>
<p>TypeScript (when using <code>strictNullChecks</code>) is one of the better languages when it comes to avoiding the dangers of <code>null</code>, as the compiler and type system can warn us when <code>null</code> rears its ugly head.</p>
<p>However, there are some cases where TypeScript's nullable types (<code>null</code> and <code>undefined</code>) can still impact your code in a negative way.</p>
<p>Today we'll be exploring how to stack optionals in TypeScript and where <code>null</code> and <code>undefined</code> fall short.</p>
<h2 id="setting-up">Setting Up</h2>
<p>This exercise starts, as they often do, with a type. We'll define a <code>PersonTraits</code> object to model the traits of a person:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>PersonTraits </span><span style="color:#89ddff;">{
</span><span> firstName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> lastName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>Nothing fancy, just a first and last name.</p>
<p>For the purposes of this exercise, we'll also define some additional types and function signatures:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>Person </span><span style="color:#c792ea;">extends </span><span style="color:#c3e88d;">PersonTraits </span><span style="color:#89ddff;">{
</span><span> id</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">declare </span><span style="font-style:italic;color:#c792ea;">function </span><span style="color:#82aaff;">findPerson</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">id</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">): </span><span>Promise</span><span style="color:#89ddff;"><</span><span>Person</span><span style="color:#89ddff;">>;
</span><span>
</span><span style="color:#c792ea;">declare </span><span style="font-style:italic;color:#c792ea;">function </span><span style="color:#82aaff;">savePerson</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">person</span><span style="color:#89ddff;">: </span><span>Person</span><span style="color:#89ddff;">): </span><span>Promise</span><span style="color:#89ddff;"><</span><span>Person</span><span style="color:#89ddff;">>;
</span></code></pre>
<p>The mechanics of these functions aren't really important, but you can imagine these would talk to some persistence layer, like a database.</p>
<p>We can now write an <code>updatePerson</code> function that we can use to update the properties of a <code>Person</code>:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#c792ea;">async </span><span style="font-style:italic;color:#c792ea;">function </span><span style="color:#82aaff;">updatePerson</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">id</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">updated</span><span style="color:#89ddff;">: </span><span>PersonTraits</span><span style="color:#89ddff;">) {
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>person </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">findPerson</span><span>(id)</span><span style="color:#89ddff;">;
</span><span>
</span><span> person</span><span style="color:#89ddff;">.</span><span>firstName </span><span style="color:#89ddff;">= </span><span>updated</span><span style="color:#89ddff;">.</span><span>firstName</span><span style="color:#89ddff;">;
</span><span> person</span><span style="color:#89ddff;">.</span><span>lastName </span><span style="color:#89ddff;">= </span><span>updated</span><span style="color:#89ddff;">.</span><span>lastName</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#82aaff;">savePerson</span><span>(person)</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>To update a person, we could do this:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> firstName</span><span style="color:#89ddff;">: '</span><span style="color:#c3e88d;">Donald</span><span style="color:#89ddff;">',
</span><span> lastName</span><span style="color:#89ddff;">: '</span><span style="color:#c3e88d;">Duck</span><span style="color:#89ddff;">',
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<h2 id="partial-people">Partial People</h2>
<p>At this point we may have realized that having to pass every property of a <code>Person</code> every time we want to update just one could become rather cumbersome and error-prone. If we only want to update <code>lastName</code> but have to pass in a <code>firstName</code> as well, we run the risk of unintentionally modifying the <code>firstName</code> if the value does not match the current one.</p>
<p>We can update our implementation to allow us to only pass in the properties that we actually want to change:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#c792ea;">async </span><span style="font-style:italic;color:#c792ea;">function </span><span style="color:#82aaff;">updatePerson</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">id</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">updated</span><span style="color:#89ddff;">: </span><span>Partial</span><span style="color:#89ddff;"><</span><span>PersonTraits</span><span style="color:#89ddff;">>) {
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>person </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">findPerson</span><span>(id)</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span>(</span><span style="color:#89ddff;">typeof </span><span>updated</span><span style="color:#89ddff;">.</span><span>firstName </span><span style="color:#89ddff;">!== '</span><span style="color:#c3e88d;">undefined</span><span style="color:#89ddff;">'</span><span>) </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>firstName </span><span style="color:#89ddff;">= </span><span>updated</span><span style="color:#89ddff;">.</span><span>firstName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span>(</span><span style="color:#89ddff;">typeof </span><span>updated</span><span style="color:#89ddff;">.</span><span>lastName </span><span style="color:#89ddff;">!== '</span><span style="color:#c3e88d;">undefined</span><span style="color:#89ddff;">'</span><span>) </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>lastName </span><span style="color:#89ddff;">= </span><span>updated</span><span style="color:#89ddff;">.</span><span>lastName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#82aaff;">savePerson</span><span>(person)</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>Now we are able to pass just the properties that we actually want to update, leaving the rest unchanged:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> firstName</span><span style="color:#89ddff;">: '</span><span style="color:#c3e88d;">Dolan</span><span style="color:#89ddff;">',
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<h2 id="adding-optional-properties">Adding Optional Properties</h2>
<p>Suppose we need to capture some optional data about a person in our <code>PersonTraits</code> type. This could be anything, but for our purposes we'll go with storing a person's favorite bird:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>PersonTraits </span><span style="color:#89ddff;">{
</span><span> firstName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> lastName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> favoriteBird</span><span style="color:#89ddff;">?: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>We're making this an optional property because a person may not have a favorite bird. There could be any number of reasons for this. Maybe they like all birds and can't pick just one as a favorite? Or perhaps they're against having a favorite <em>anything</em>? Of course, we can't ignore that some people just don't like birds.</p>
<p>Whatever the reason, we now have this <code>favoriteBird</code> property that may or may not have a value.</p>
<p>We can modify our <code>updatePerson</code> function to handle updates to <code>favoriteBird</code>:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#c792ea;">async </span><span style="font-style:italic;color:#c792ea;">function </span><span style="color:#82aaff;">updatePerson</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">id</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">updated</span><span style="color:#89ddff;">: </span><span>Partial</span><span style="color:#89ddff;"><</span><span>PersonTraits</span><span style="color:#89ddff;">>) {
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>person </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">findPerson</span><span>(id)</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span>(</span><span style="color:#89ddff;">typeof </span><span>updated</span><span style="color:#89ddff;">.</span><span>firstName </span><span style="color:#89ddff;">!== '</span><span style="color:#c3e88d;">undefined</span><span style="color:#89ddff;">'</span><span>) </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>firstName </span><span style="color:#89ddff;">= </span><span>updated</span><span style="color:#89ddff;">.</span><span>firstName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span>(</span><span style="color:#89ddff;">typeof </span><span>updated</span><span style="color:#89ddff;">.</span><span>lastName </span><span style="color:#89ddff;">!== '</span><span style="color:#c3e88d;">undefined</span><span style="color:#89ddff;">'</span><span>) </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>lastName </span><span style="color:#89ddff;">= </span><span>updated</span><span style="color:#89ddff;">.</span><span>lastName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">if </span><span>(</span><span style="color:#89ddff;">typeof </span><span>updated</span><span style="color:#89ddff;">.</span><span>favoriteBird </span><span style="color:#89ddff;">!== '</span><span style="color:#c3e88d;">undefined</span><span style="color:#89ddff;">'</span><span>) </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>favoriteBird </span><span style="color:#89ddff;">= </span><span>updated</span><span style="color:#89ddff;">.</span><span>favoriteBird</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#82aaff;">savePerson</span><span>(person)</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>With that in place, we can now update a person's favorite bird:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> favoriteBird</span><span style="color:#89ddff;">: '</span><span style="color:#c3e88d;">duck</span><span style="color:#89ddff;">',
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<p>This may seem fine, but we now have a problem: our <code>updatePerson</code> function can never clear the <code>favoriteBird</code> property.</p>
<p>If we try to clear it, we'll see this has no effect:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span style="color:#f78c6c;">undefined</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<h2 id="the-problem">The Problem</h2>
<p>Let's unpack the problem a bit.</p>
<p>When we first started off, we had a type where every property was required. We then used the <code>Partial</code> type to create a variant of <code>PersonTraits</code> where every property could be optional:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#4a4a4a;">// `Partial<PersonTraits>` is the same as:
</span><span style="font-style:italic;color:#c792ea;">type </span><span>PartialPersonTraits </span><span style="color:#89ddff;">= {
</span><span> firstName</span><span style="color:#89ddff;">?: </span><span style="color:#ffcb6b;">string </span><span style="color:#89ddff;">| </span><span style="color:#ffcb6b;">undefined</span><span style="color:#89ddff;">;
</span><span> lastName</span><span style="color:#89ddff;">?: </span><span style="color:#ffcb6b;">string </span><span style="color:#89ddff;">| </span><span style="color:#ffcb6b;">undefined</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">};
</span></code></pre>
<p>In our <code>updatePerson</code> function we could now check if the type of a given property was <code>undefined</code> to know whether the value was absent.</p>
<p>However, this approach falls apart when an optional property is added to our <code>PersonTraits</code> type:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>PersonTraits </span><span style="color:#89ddff;">{
</span><span> firstName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> lastName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> favoriteBird</span><span style="color:#89ddff;">?: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="font-style:italic;color:#4a4a4a;">// `Partial<PersonTraits>` is the same as:
</span><span style="font-style:italic;color:#c792ea;">type </span><span>PartialPersonTraits </span><span style="color:#89ddff;">= {
</span><span> firstName</span><span style="color:#89ddff;">?: </span><span style="color:#ffcb6b;">string </span><span style="color:#89ddff;">| </span><span style="color:#ffcb6b;">undefined</span><span style="color:#89ddff;">;
</span><span> lastName</span><span style="color:#89ddff;">?: </span><span style="color:#ffcb6b;">string </span><span style="color:#89ddff;">| </span><span style="color:#ffcb6b;">undefined</span><span style="color:#89ddff;">;
</span><span> favoriteBird</span><span style="color:#89ddff;">?: </span><span style="color:#ffcb6b;">string </span><span style="color:#89ddff;">| </span><span style="color:#ffcb6b;">undefined</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">};
</span></code></pre>
<p>In both <code>PersonTraits</code> and <code>Partial<PersonTraits></code>, <code>favoriteBird</code> has the type <code>string | undefined</code>. The result of this is that we can no longer tell the difference between a value for <code>favoriteBird</code> being omitted or a value of <code>undefined</code> being passed.</p>
<p>The underlying problem here is that TypeScript does not support stacking optional types. Defining a type of <code>string | undefined | undefined</code> is the same as <code>string | undefined</code>, due to flattening.</p>
<blockquote>
<p>While it is possible to simulate one level of optional stacking by making use of both <code>null</code> and <code>undefined</code>, this does not serve as a general-purpose solution to the problem.</p>
</blockquote>
<h2 id="introducing-option">Introducing Option</h2>
<p>The good news is there is a solution to our problem!</p>
<p>The <code>Option</code> type (also known as <code>Maybe</code>) is a container type that can be used to model a value that may or may not have a value. This type can be defined in TypeScript using a <a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions">discriminated union</a>:</p>
<!-- prettier-ignore -->
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">type </span><span>Option</span><span style="color:#89ddff;"><</span><span>T</span><span style="color:#89ddff;">> =
</span><span> </span><span style="color:#89ddff;">| { </span><span>tag</span><span style="color:#89ddff;">: '</span><span style="color:#c3e88d;">Some</span><span style="color:#89ddff;">'; </span><span>value</span><span style="color:#89ddff;">: </span><span>T </span><span style="color:#89ddff;">}
</span><span> </span><span style="color:#89ddff;">| { </span><span>tag</span><span style="color:#89ddff;">: '</span><span style="color:#c3e88d;">None</span><span style="color:#89ddff;">' };
</span></code></pre>
<p>If the <code>tag</code> is <code>'Some'</code> then the <code>Option</code> contains a <code>value</code> of type <code>T</code>. If the <code>tag</code> is <code>'None'</code> then the <code>Option</code> represents the absence of a value.</p>
<p>We're going to be using <a href="https://github.com/gcanti/fp-ts"><code>fp-ts</code></a>—a functional programming library for TypeScript—for this, which comes with its own <code>Option</code> type out of the box, so we won't need to define it ourselves.</p>
<blockquote>
<p>If you're following along, you may have to replace <code>fp-ts/</code> with <code>fp-ts/lib/</code> in the import statements, depending on your module configuration.</p>
</blockquote>
<h2 id="a-partial-for-options">A Partial for Options</h2>
<p>We can define our own <a href="https://www.typescriptlang.org/docs/handbook/2/mapped-types.html">mapped type</a> that behaves like <code>Partial</code>, but with an additional layer of optionality powered by <code>Option</code>:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>Option </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">fp-ts/Option</span><span style="color:#89ddff;">';
</span><span>
</span><span style="font-style:italic;color:#c792ea;">type </span><span>PartialOption</span><span style="color:#89ddff;"><</span><span>T</span><span style="color:#89ddff;">> = {
</span><span> [P </span><span style="color:#89ddff;">in keyof </span><span>T]</span><span style="color:#89ddff;">?: </span><span>Option</span><span style="color:#89ddff;"><</span><span>T[P]</span><span style="color:#89ddff;">>;
</span><span style="color:#89ddff;">};
</span></code></pre>
<p>We can then use <code>PartialOption</code> in the same way we used <code>Partial</code> before:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>identity</span><span style="color:#89ddff;">, </span><span>pipe </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">fp-ts/function</span><span style="color:#89ddff;">';
</span><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#f78c6c;">* </span><span style="font-style:italic;color:#c792ea;">as </span><span>O </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">fp-ts/Option</span><span style="color:#89ddff;">';
</span><span>
</span><span style="color:#c792ea;">async </span><span style="font-style:italic;color:#c792ea;">function </span><span style="color:#82aaff;">updatePerson</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">id</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">updated</span><span style="color:#89ddff;">: </span><span>PartialOption</span><span style="color:#89ddff;"><</span><span>PersonTraits</span><span style="color:#89ddff;">>) {
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>person </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">findPerson</span><span>(id)</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span style="color:#82aaff;">noop </span><span style="color:#89ddff;">= (): </span><span style="color:#ffcb6b;">void </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#f78c6c;">undefined</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>withUpdatedValue </span><span style="color:#89ddff;">=
</span><span> </span><span style="color:#89ddff;"><</span><span>T</span><span style="color:#89ddff;">>(</span><span style="color:#82aaff;">f</span><span style="color:#89ddff;">: (</span><span style="color:#f78c6c;">value</span><span style="color:#89ddff;">: </span><span>T</span><span style="color:#89ddff;">) </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#ffcb6b;">void</span><span style="color:#89ddff;">) </span><span style="font-style:italic;color:#c792ea;">=>
</span><span> </span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">property</span><span style="color:#89ddff;">: </span><span>Option</span><span style="color:#89ddff;"><</span><span>T</span><span style="color:#89ddff;">> | </span><span style="color:#ffcb6b;">undefined</span><span style="color:#89ddff;">) </span><span style="font-style:italic;color:#c792ea;">=>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(property</span><span style="color:#89ddff;">, </span><span>O</span><span style="color:#89ddff;">.</span><span>fromNullable</span><span style="color:#89ddff;">, </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">chain</span><span>(identity)</span><span style="color:#89ddff;">, </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">match</span><span>(noop</span><span style="color:#89ddff;">, </span><span>f))</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> updated</span><span style="color:#89ddff;">.</span><span>firstName</span><span style="color:#89ddff;">,
</span><span> </span><span style="color:#82aaff;">withUpdatedValue</span><span>(</span><span style="color:#f78c6c;">firstName </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>firstName </span><span style="color:#89ddff;">= </span><span>firstName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">,
</span><span> )</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> updated</span><span style="color:#89ddff;">.</span><span>lastName</span><span style="color:#89ddff;">,
</span><span> </span><span style="color:#82aaff;">withUpdatedValue</span><span>(</span><span style="color:#f78c6c;">lastName </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>lastName </span><span style="color:#89ddff;">= </span><span>lastName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">,
</span><span> )</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> updated</span><span style="color:#89ddff;">.</span><span>favoriteBird</span><span style="color:#89ddff;">,
</span><span> </span><span style="color:#82aaff;">withUpdatedValue</span><span>(</span><span style="color:#f78c6c;">favoriteBird </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>favoriteBird </span><span style="color:#89ddff;">= </span><span>favoriteBird</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">,
</span><span> )</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#82aaff;">savePerson</span><span>(person)</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>The implementation of this function looks a little different from before, so let's take a peek at what's going on under the hood.</p>
<p>The first thing to note is that we're now using <code>pipe</code> from <code>fp-ts</code> to streamline our code. <code>pipe</code> takes a value and then pipes it through a pipeline of functions, like so:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">const </span><span>n </span><span style="color:#89ddff;">= </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> </span><span style="color:#f78c6c;">3</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;"> </span><span style="font-style:italic;color:#4a4a4a;">// x === 3
</span><span> </span><span style="color:#f78c6c;">x </span><span style="font-style:italic;color:#c792ea;">=> </span><span>x </span><span style="color:#89ddff;">* </span><span style="color:#f78c6c;">2</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;"> </span><span style="font-style:italic;color:#4a4a4a;">// y === 6
</span><span> </span><span style="color:#f78c6c;">y </span><span style="font-style:italic;color:#c792ea;">=> </span><span>y </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">,
</span><span>)</span><span style="color:#89ddff;">;
</span><span style="font-style:italic;color:#4a4a4a;">// n === 7
</span></code></pre>
<p>The other is that we've added a <code>withUpdatedValue</code> helper function to cut down on some of the boilerplate needed for handling each updated value. Here it is again with some explanatory comments:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#4a4a4a;">// A function that does nothing (a no-op), used for discarding a `None` value.
</span><span style="font-style:italic;color:#c792ea;">const </span><span style="color:#82aaff;">noop </span><span style="color:#89ddff;">= (): </span><span style="color:#ffcb6b;">void </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#f78c6c;">undefined</span><span style="color:#89ddff;">;
</span><span>
</span><span style="font-style:italic;color:#c792ea;">const </span><span>withUpdatedValue </span><span style="color:#89ddff;">=
</span><span> </span><span style="color:#89ddff;"><</span><span>T</span><span style="color:#89ddff;">>(</span><span style="color:#82aaff;">f</span><span style="color:#89ddff;">: (</span><span style="color:#f78c6c;">value</span><span style="color:#89ddff;">: </span><span>T</span><span style="color:#89ddff;">) </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#ffcb6b;">void</span><span style="color:#89ddff;">) </span><span style="font-style:italic;color:#c792ea;">=>
</span><span> </span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">property</span><span style="color:#89ddff;">: </span><span>Option</span><span style="color:#89ddff;"><</span><span>T</span><span style="color:#89ddff;">> | </span><span style="color:#ffcb6b;">undefined</span><span style="color:#89ddff;">) </span><span style="font-style:italic;color:#c792ea;">=>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> property</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;"> </span><span style="font-style:italic;color:#4a4a4a;">// Convert from an `Option<T> | undefined` to an `Option<Option<T>>`
</span><span> O</span><span style="color:#89ddff;">.</span><span>fromNullable</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;"> </span><span style="font-style:italic;color:#4a4a4a;">// Flatten an `Option<Option<T>>` to `Option<T>`
</span><span> O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">chain</span><span>(identity)</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;"> </span><span style="font-style:italic;color:#4a4a4a;">// If `Option<T>` is `Some` we extract the value and pass it as the argument
</span><span style="color:#89ddff;"> </span><span style="font-style:italic;color:#4a4a4a;">// to `f`, otherwise we call `noop` to do nothing.
</span><span> O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">match</span><span>(noop</span><span style="color:#89ddff;">, </span><span>f)</span><span style="color:#89ddff;">,
</span><span> )</span><span style="color:#89ddff;">;
</span></code></pre>
<p>With our new <code>updatePerson</code> in place, we can now solve our original issue:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">const </span><span>withFavoriteBird </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">goose</span><span style="color:#89ddff;">'</span><span>)</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span><span>
</span><span style="font-style:italic;color:#c792ea;">const </span><span>noFavoriteBird </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(</span><span style="color:#f78c6c;">undefined</span><span>)</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<h2 id="going-all-in-on-option">Going All-in on Option</h2>
<p>While our <code>PartialOption</code> type did solve the optional stacking issue from before, it was a little confusing having to deal with both <code>undefined</code> and <code>Option</code>, especially when converting from one to the other.</p>
<p>Let's take a look at what this would look like if we go all-in on <code>Option</code>.</p>
<p>The first thing we do is change the type of the <code>favoriteBird</code> property:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">interface </span><span>PersonTraits </span><span style="color:#89ddff;">{
</span><span> firstName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> lastName</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">;
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>Option</span><span style="color:#89ddff;"><</span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">>;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>We are now using an <code>Option</code> to model the requirement that a person may or may not have a favorite bird.</p>
<p>We'll also need to create an equivalent type to <code>Partial</code> that works with <code>Option</code> instead of <code>undefined</code>:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">import </span><span style="color:#89ddff;">{ </span><span>Option </span><span style="color:#89ddff;">} </span><span style="font-style:italic;color:#c792ea;">from </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">fp-ts/Option</span><span style="color:#89ddff;">';
</span><span>
</span><span style="font-style:italic;color:#c792ea;">type </span><span>Optional</span><span style="color:#89ddff;"><</span><span>T</span><span style="color:#89ddff;">> = {
</span><span> [P </span><span style="color:#89ddff;">in keyof </span><span>T]</span><span style="color:#89ddff;">: </span><span>Option</span><span style="color:#89ddff;"><</span><span>T[P]</span><span style="color:#89ddff;">>;
</span><span style="color:#89ddff;">};
</span></code></pre>
<p>Using this <code>Optional</code> type with <code>updatePerson</code>, we can see that our implementation looks much closer to what we had back when we were dealing with <code>undefined</code>s, without the need for a complex helper function:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="color:#c792ea;">async </span><span style="font-style:italic;color:#c792ea;">function </span><span style="color:#82aaff;">updatePerson</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">id</span><span style="color:#89ddff;">: </span><span style="color:#ffcb6b;">string</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">updated</span><span style="color:#89ddff;">: </span><span>Optional</span><span style="color:#89ddff;"><</span><span>PersonTraits</span><span style="color:#89ddff;">>) {
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span>person </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">findPerson</span><span>(id)</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">const </span><span style="color:#82aaff;">noop </span><span style="color:#89ddff;">= (): </span><span style="color:#ffcb6b;">void </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#f78c6c;">undefined</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> updated</span><span style="color:#89ddff;">.</span><span>firstName</span><span style="color:#89ddff;">,
</span><span> O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">match</span><span>(noop</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">firstName </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>firstName </span><span style="color:#89ddff;">= </span><span>firstName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">,
</span><span> )</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> updated</span><span style="color:#89ddff;">.</span><span>lastName</span><span style="color:#89ddff;">,
</span><span> O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">match</span><span>(noop</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">lastName </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>lastName </span><span style="color:#89ddff;">= </span><span>lastName</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">,
</span><span> )</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="color:#82aaff;">pipe</span><span>(
</span><span> updated</span><span style="color:#89ddff;">.</span><span>favoriteBird</span><span style="color:#89ddff;">,
</span><span> O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">match</span><span>(noop</span><span style="color:#89ddff;">, </span><span style="color:#f78c6c;">favoriteBird </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">{
</span><span> person</span><span style="color:#89ddff;">.</span><span>favoriteBird </span><span style="color:#89ddff;">= </span><span>favoriteBird</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">,
</span><span> )</span><span style="color:#89ddff;">;
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#82aaff;">savePerson</span><span>(person)</span><span style="color:#89ddff;">;
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>This works very much the same way as before:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">const </span><span>withFavoriteBird </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> firstName</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span>none</span><span style="color:#89ddff;">,
</span><span> lastName</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span>none</span><span style="color:#89ddff;">,
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">goose</span><span style="color:#89ddff;">'</span><span>))</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span><span>
</span><span style="font-style:italic;color:#c792ea;">const </span><span>noFavoriteBird </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> firstName</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span>none</span><span style="color:#89ddff;">,
</span><span> lastName</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span>none</span><span style="color:#89ddff;">,
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(O</span><span style="color:#89ddff;">.</span><span>none)</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<p>One thing to note is that we now have to pass a <code>None</code> for every property we don't want to update. We could clean this up a bit by adding a default object containing all <code>None</code>s and then passing just the properties we want to update:</p>
<pre data-lang="ts" style="background-color:#212121;color:#eeffff;" class="language-ts "><code class="language-ts" data-lang="ts"><span style="font-style:italic;color:#c792ea;">const </span><span>updatedPersonDefault</span><span style="color:#89ddff;">: </span><span>Optional</span><span style="color:#89ddff;"><</span><span>PersonTraits</span><span style="color:#89ddff;">> = {
</span><span> firstName</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span>none</span><span style="color:#89ddff;">,
</span><span> lastName</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span>none</span><span style="color:#89ddff;">,
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span>none</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">};
</span><span>
</span><span style="font-style:italic;color:#c792ea;">const </span><span>withFavoriteBird </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> </span><span style="color:#89ddff;">...</span><span>updatedPersonDefault</span><span style="color:#89ddff;">,
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">goose</span><span style="color:#89ddff;">'</span><span>))</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span><span>
</span><span style="font-style:italic;color:#c792ea;">const </span><span>noFavoriteBird </span><span style="color:#89ddff;">= </span><span style="font-style:italic;color:#c792ea;">await </span><span style="color:#82aaff;">updatePerson</span><span>(</span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">1</span><span style="color:#89ddff;">', {
</span><span> </span><span style="color:#89ddff;">...</span><span>updatedPersonDefault</span><span style="color:#89ddff;">,
</span><span> favoriteBird</span><span style="color:#89ddff;">: </span><span>O</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">some</span><span>(O</span><span style="color:#89ddff;">.</span><span>none)</span><span style="color:#89ddff;">,
</span><span style="color:#89ddff;">}</span><span>)</span><span style="color:#89ddff;">;
</span></code></pre>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>We've now seen one instance where <code>null</code> and <code>undefined</code> fall short and how making use of <code>Option</code> can overcome these shortcomings.</p>
<p>If you're using TypeScript today, I'd encourage you to check out <a href="https://github.com/gcanti/fp-ts"><code>fp-ts</code></a> to improve your TypeScript code. Even a slight sprinkling of the many features it provides can make a world of difference.</p>
Rediscovering Creativity2021-03-02T04:58:53.134+00:002021-03-02T04:58:53.134+00:00Unknownhttps://maxdeviant.com/shards/2021/rediscovering-creativity/<p>Three years ago I took a page out of Mike Winkelmann's book and started my own <a href="https://www.beeple-crap.com/everydays">everydays</a> project. For an entire year I produced and released a piece of art into the world each and every day.</p>
<p>This year of everydays was the most creative period of my life so far. The act of sitting down and creating every day spilled over into other areas. I ended up dabbling in music production as well, participating in WeeklyBeats 2018 and releasing a number of tracks.</p>
<p>Upon hitting the one year mark, I stopped doing my everydays. This led to a drop in my other creative outlets as well.</p>
<p>While there are certainly a number of factors that could contribute to this, I think that the loss of forcing myself to sit down and create for an hour or two a day played the biggest role in this.</p>
<p>In an attempt to rediscover and rekindle my creativity, I've started a new round of everydays. We'll see how the resumption of this activity affects other areas of my life.</p>
Death and Cryptography2020-12-08T05:08:39.526+00:002020-12-08T05:08:39.526+00:00Unknownhttps://maxdeviant.com/shards/2020/death-and-cryptography/<p>What happens to your non-physical possessions when you die? It's a rather morbid thing to ponder, I know, but bear with me.</p>
<p>Physical possessions are often left to another person—surviving partner or children, perhaps—through the execution of a <a href="https://en.wikipedia.org/wiki/Will_and_testament">will</a>. Some things, especially home furnishings, may end up in antique or consignment shops. In this sense some of these possessions that you once called your own receive new life at the hands of their new owners.</p>
<p>What about digital possessions? Will anyone care for your open source projects or your digital media libary after your departure from this world? While the answer to this may depend heavily on the level of interest in these digital works, the more immediate problem is one of access.</p>
<p>A complication that arises with digital possessions is that these are often locked away somewhere behind a set of credentials. Without possession of these credentials, the digital possessions are not accessible. Password managers help to alleviate this problem, as a well-curated password manager can hold the keys to any number of digital archives. All that is needed is to distribute the master password after your demise and the inheritors now hold all of the keys.</p>
<p>These sorts of provisions, while practical, are a little too familiar for my liking. I'm more interested in exploring more niche ideas in this space.</p>
<p>What if you generated a <a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy">PGP</a> keypair and stored the private key as part of your will? Over the course of your life you could then write journal entries and encrypt these using the public key. These writings could be stored in plain sight, but would be inaccessible to all until the release of your "<a href="https://en.wikipedia.org/wiki/Dead_man%27s_switch">dead man's</a>" private key.</p>
<p>I'll have to dwell on this more, as I find these possibilities intriguing.</p>
Introducing Shards2020-12-06T19:06:46.922+00:002020-12-06T19:06:46.922+00:00Unknownhttps://maxdeviant.com/shards/2020/introducing-shards/<blockquote>
<p>I really need to take a page out of <a href="https://twitter.com/brandur">@brandur</a>'s book and add a "fragments" section to my site where I can post things too long-form for Twitter but not quite worthy of a full-blown blog post</p>
<p>— <a href="https://twitter.com/maxdeviant/status/1317110017733132292">@maxdeviant</a>, Oct 16, 2020</p>
</blockquote>
<p>After much procrastination and anticipation on my part (and no one else's), I'm excited to introduce you to shards, my solution to spontaneous writing.</p>
<p>The name stems from the dictionary definition:</p>
<blockquote>
<p><strong>shard</strong> · <em>/SHärd/</em> · <em>noun</em> — a piece of broken ceramic, metal, glass, or rock, typically having sharp edges.</p>
</blockquote>
<p>Like shards of glass, these these are my rough, uncut thoughts that have not yet been polished enough to make their way into a proper <a href="https://maxdeviant.com/posts/">post</a>. While some of the ideas may eventually be refined over time and reappear in a post, I'm perfectly content to have the majority stand alone. My hope is that as I arrange these jagged shards together perhaps I can piece together enough such that I can discern something new in their combined reflections.</p>
<p>This endeavor has been unduly inspired by Brandur's <a href="https://brandur.org/fragments">Fragments</a> and David R. MacIver's <a href="https://notebook.drmaciver.com/">Notebook</a>, specifically his post on <a href="https://notebook.drmaciver.com/posts/2020-06-08-10:11.html">starting a daily writing practice</a>.</p>
<p>Through the combination of shards and recent improvements I've made to automate the publishing of my website, I have set myself up for success in writing more frequently.</p>
<p>All that's left to do now is <strong>write.</strong></p>
WorkOS and NixOS2020-11-08T00:00:00+00:002020-11-08T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0015-workos-and-nix-on-macos/<p>Greetings, travelers.</p>
<p>It's been some time since I last wrote one of these. The past few months have been rather difficult for me, which ultimately led to my radio silence, but I'm happy to report that things are looking up.</p>
<h2 id="joining-workos">Joining WorkOS</h2>
<p>This past week was my first week as a software engineer at <a href="https://workos.com/">WorkOS</a>. I couldn't be any more excited about joining the team here, and I already feel like I'm starting to settle in.</p>
<h2 id="nix-on-macos">Nix on macOS</h2>
<p>As part of setting up my new work machine, I started delving into using Nix on macOS. I was a little apprehensive going into this, as using Nix on macOS—especially Catalina—is reportedly <a href="https://github.com/NixOS/nix/issues/2925">problematic</a>. I'm not one to let rough edges stand in my way, so I decided to give it a try.</p>
<p>Thankfully, the aforementioned issues with Nix on Catalina have been partially addressed as of Nix 2.3.8. To install Nix on macOS, you can pass the <code>--darwin-use-unencrypted-nix-store-volume</code> flag to the install script, as suggested by the <a href="https://hydra.nixos.org/build/119559243/download/1/manual/#sect-macos-installation">docs</a>:</p>
<pre data-lang="sh" style="background-color:#212121;color:#eeffff;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#82aaff;">sh </span><span style="color:#89ddff;"><(</span><span style="color:#82aaff;">curl https://nixos.org/nix/install</span><span style="color:#89ddff;">) --</span><span style="color:#f78c6c;">darwin-use-unencrypted-nix-store-volume
</span></code></pre>
<p>I initially had some trouble with this, as running the script seemed to do nothing. After a bit of debugging, I realized that <code>https://nixos.org/nix/install</code> is actually just a redirect to the install script for the latest version of Nix. Currently, it redirects to <code>https://releases.nixos.org/nix/nix-2.3.8/install</code>.</p>
<p>I figured <code>curl</code> must not like resolving the redirect, so I subbed in the resolved link and the installation went down without a hitch:</p>
<pre data-lang="sh" style="background-color:#212121;color:#eeffff;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#82aaff;">sh </span><span style="color:#89ddff;"><(</span><span style="color:#82aaff;">curl https://releases.nixos.org/nix/nix-2.3.8/install</span><span style="color:#89ddff;">) --</span><span style="color:#f78c6c;">darwin-use-unencrypted-nix-store-volume
</span></code></pre>
<p>Once Nix was installed I went about installing <a href="https://github.com/nix-community/home-manager">Home Manager</a>, which is my preferred way of managing my program and dotfiles:</p>
<pre data-lang="sh" style="background-color:#212121;color:#eeffff;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#82aaff;">nix-channel</span><span style="color:#89ddff;"> --</span><span style="color:#f78c6c;">add</span><span style="color:#82aaff;"> https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
</span><span style="color:#82aaff;">nix-channel</span><span style="color:#89ddff;"> --</span><span style="color:#f78c6c;">update
</span><span>
</span><span style="color:#82aaff;">nix-shell </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;"><home-manager></span><span style="color:#89ddff;">' -</span><span style="color:#f78c6c;">A</span><span style="color:#82aaff;"> install
</span></code></pre>
<p>I'm a <a href="https://fishshell.com/">fish</a> user, and in order to get fish to recognize my Nix environment on startup I had to add the following to my Nix-managed fish initialization script:</p>
<pre data-lang="sh" style="background-color:#212121;color:#eeffff;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#82aaff;">set </span><span style="color:#89ddff;">-</span><span style="color:#f78c6c;">p</span><span style="color:#82aaff;"> fish_function_path </span><span style="color:#89ddff;">${</span><span>pkgs.fish</span><span style="color:#89ddff;">-</span><span style="color:#82aaff;">foreign-env</span><span style="color:#89ddff;">}</span><span style="color:#82aaff;">/share/fish-foreign-env/functions
</span><span style="color:#82aaff;">fenv source </span><span style="color:#89ddff;">${</span><span>config.home.profileDirectory</span><span style="color:#89ddff;">}</span><span style="color:#82aaff;">/etc/profile.d/nix.sh </span><span style="color:#89ddff;">></span><span style="color:#82aaff;"> /dev/null
</span><span style="color:#82aaff;">set </span><span style="color:#89ddff;">-</span><span style="color:#f78c6c;">e</span><span style="color:#82aaff;"> fish_function_path</span><span style="font-style:italic;color:#c792ea;">[</span><span style="color:#82aaff;">1</span><span style="font-style:italic;color:#c792ea;">]
</span></code></pre>
<p>This sources the <code>~/etc/profile.d/nix.sh</code> file using <a href="https://github.com/oh-my-fish/plugin-foreign-env"><code>fenv</code></a>, which saved me from having to port the entirety of <code>nix.sh</code> by hand to fish.</p>
<p>I'm still in the process of setting everything up, but my <a href="https://github.com/maxdeviant/dotfiles/tree/master/hosts/ringed-city">dotfiles</a> are available on GitHub if you would like to check them out.</p>
<p>At some point I also plan to look into <a href="https://github.com/LnL7/nix-darwin"><code>nix-darwin</code></a> as a way to manage more of my Mac's configuration through Nix.</p>
<p>If anyone else has had any success (or lack thereof) running Nix on macOS, I'd love to hear from you.</p>
<p>Until next time,</p>
<p>Marshall</p>
The High Cost of Caring2020-07-12T19:51:12.537+00:002020-07-12T19:51:12.537+00:00Unknownhttps://maxdeviant.com/posts/2020/the-high-cost-of-caring/<p>I care too much. This is a fact.</p>
<p>In particular, I care too much about the art of building software.</p>
<p>As time goes on, software continues to become an increasingly present and critical element in our world. I take my job as a software practitioner incredibly seriously, as I believe it is my ethical responsibility to do so. As a result, I care deeply about things like technical excellence, code quality and maintainability, and treating code with the same importance as the end deliverable.</p>
<p>This care does not come without cost. On the contrary, the cost of caring is enormously high. I routinely suffer from high levels of stress and anxiety as a result of my caring about a particular matter. I am especially inundated with these adverse effects when I'm surrounded by people who don't care as much as I do.</p>
<p>Would it be healthier for me to not care so much? Almost certainly. At the very least it may slow the rate at which gray hairs are appearing on my head.</p>
<p>But, for me, not caring is not an option. I have the choice between caring too much or becoming entirely apathetic. There is no in-between. Presented with these two extremes, I would choose caring every single time.</p>
<p>Not caring is lazy. It takes work to care. There is a high cost to caring, but it is absolutely worth it.</p>
Animal Escapism2020-05-11T00:00:00+00:002020-05-11T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0014-animal-escapism/<p>Greetings, travelers.</p>
<p>It seems that everyone around me is playing <a href="https://www.animal-crossing.com/new-horizons/">Animal Crossing</a> these days. I say "around me", but what I really mean is "within my social sphere and also on the internet". There's only one individual—my wife—who is physically close enough to me in order to satisfy the requirements of being "around". As someone who partook quite heavily in previous versions of Animal Crossing in my childhood, it's somewhat surprising that I'm not choosing to indulge now. After all, I've been stuck indoors for as long as I can remember, and it doesn't seem like I'll be getting out anytime soon.</p>
<p>My avoidance of Animal Crossing is in part related to physical constraints. Nintendo decided, regrettably, that players' islands in New Horizons should be limited to one per Switch console, as opposed to the more reasonable alternative of associating them with an individual user profile. Seeing as we only have one Switch, it would seem that having two separate islands is too big an ask. For all of the charm that Nintendo is able to capture in its titles, the company has a horrendous propensity towards cocking up basic quality of life features. Don't even get me started on how your islands can't be transferred between different consoles either.</p>
<p>Physical constraints notwithstanding, I just don't know how much I would enjoy Animal Crossing at this point in my life. The gameplay just seems tiresome and lacking in the repeated hits of dopamine that I find myself desiring in the titles I choose to play. The passage of time that matches the real world is probably the biggest thing putting me off from Animal Crossing. When I play video games I seek an escape from the real world, not something that is bound by the same laws of temporality.</p>
<p>Lately I have been escaping the tedium of reality by revisiting a different game from my childhood: RuneScape (specifically Old School RuneScape). I have played this game on-and-off for years, and it has somehow managed to retain its allure over time. While there are periods where I get tired of playing or take time off to engage in other pursuits, I somehow always come back to it.</p>
<p>I'm still undecided on how I feel about my use of video games as a form of escapism. Sometimes I feel guilty for playing them for hours at a time to forget about the cares of the world. I think part of this stems from my observation that people tend to stigmatize video games more than other forms of entertainment. While I entirely reject the notion that watching TV for some <em>n</em> hours is somehow preferable to playing a video game for the same amount of time, I still have a tendency to feel guilty after an extended gaming session.</p>
<p>I have noticed that as I've gotten older I tend to alternate more between different moods, specifically productive and consumptive ones. One week I may feel like playing video games, while the next I want to pour myself into the creation of something. I'm learning to embrace these moods as they come and let them guide me where they may. I find the outcome of this approach much more satisfying than attempting to fight against my innermost self.</p>
<p>Marshall</p>
Mo Media Keys Mo Problems2020-04-23T00:00:00+00:002020-04-23T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0013-mo-media-keys-mo-problems/<p>It's hard to believe that there is only a week left in April. This month has managed to somehow fly by while still feeling like an absolute eternity.</p>
<p>With all the time stuck indoors, I decided to sit down and try to get my keyboard's<sup class="footnote-reference"><a href="#1">1</a></sup> media keys configured to work with my NixOS setup. While this may sound like a trivial exercise, it ended up being a decent amount of effort to get everything setup.</p>
<p>For context, I run <a href="https://github.com/Spotifyd/spotifyd"><code>spotifyd</code></a> to play my music from Spotify. I can't explain it, but there's something that absolutely tickles me about having your music player <a href="https://en.wikipedia.org/wiki/Daemon_(computing)">daemonized</a>. <code>spotifyd</code> supports integration with media controls through the <a href="https://specifications.freedesktop.org/mpris-spec/latest/">MPRIS D-BUS Interface Specification</a>. This functionality is behind a feature flag, which means that in order to get access to it <code>spotifyd</code> needs to be built with the flag.</p>
<p>Thanks to a coincidentally-timed <a href="https://github.com/NixOS/nixpkgs/pull/85471">pull request</a> I was able to avoid doing any work to update the Nix package for <code>spotifyd</code>. However, it did require a day or two of waiting in order for the PR to get merged and for the package registry to get rebuilt.</p>
<p>While sitting around waiting for the upstream changes I started learning about <a href="https://nixos.wiki/wiki/Overlays">Nix overlays</a>, which appeared to be the optimal route for building <code>spotifyd</code> with the right features enabled.</p>
<p>After trawling through a number of docs and tinkering around a bit, I arrived at the following snippet to add to my <code>home.nix</code> file:</p>
<pre data-lang="nix" style="background-color:#212121;color:#eeffff;" class="language-nix "><code class="language-nix" data-lang="nix"><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">nixpkgs</span><span>.</span><span style="color:#c792ea;">overlays </span><span style="color:#89ddff;">= [
</span><span> </span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">self</span><span style="color:#89ddff;">: </span><span style="color:#f78c6c;">super</span><span style="color:#89ddff;">:
</span><span> </span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">spotifyd </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">super</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">spotifyd</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">override </span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">withPulseAudio </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">true</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#c792ea;">withMpris </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">true</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">};
</span><span> </span><span style="color:#89ddff;">})
</span><span> </span><span style="color:#89ddff;">];
</span><span>
</span><span> </span><span style="font-style:italic;color:#4a4a4a;"># ...
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>This overlay replaces the <code>spotifyd</code> Nix package with a version that has the <code>withPulseAudio</code> and <code>withMpris</code> options enabled. These options, in turn, pass the corresponding feature flags when building <code>spotifyd</code> with <code>cargo</code>. In this case, the <code>withMpris</code> option is the key to turning on support for MPRIS within <code>spotifyd</code>.</p>
<p>Once <code>spotifyd</code> was setup with MPRIS support it could be controlled using an MPRIS controller, like <a href="https://github.com/altdesktop/playerctl"><code>playerctl</code></a>.</p>
<p><code>playerctl -l</code> reveals that <code>playerctl</code> can see <code>spotifyd</code> as a player:</p>
<pre data-lang="sh" style="background-color:#212121;color:#eeffff;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#82aaff;">$ playerctl</span><span style="color:#89ddff;"> -</span><span style="color:#f78c6c;">l
</span><span style="color:#82aaff;">spotifyd
</span></code></pre>
<p>The last thing I had to do was wire up the media keys to use <code>playerctl</code> to issue commands to <code>spotifyd</code>. I use <a href="https://github.com/baskerville/bspwm"><code>bspwm</code></a> as my window manager, so achieving this was just a matter of adding some lines to my <a href="https://github.com/baskerville/sxhkd">sxhkd</a> configuration:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>XF86AudioRaiseVolume
</span><span> playerctl --player=spotifyd volume .03+
</span><span>
</span><span>XF86AudioLowerVolume
</span><span> playerctl --player=spotifyd volume .03-
</span><span>
</span><span>XF86AudioPlay
</span><span> playerctl --player=spotifyd play-pause
</span><span>
</span><span>XF86AudioNext
</span><span> playerctl --player=spotifyd next
</span><span>
</span><span>XF86AudioPrev
</span><span> playerctl --player=spotifyd previous
</span></code></pre>
<p>Success! My media keys were now working as expected... almost.</p>
<p>Unfortunately, there still seems to be an issue where I can't control <code>spotifyd</code>'s volume via <code>playerctl</code>, but that's an issue for another day.</p>
<p>Now if you'll excuse me, I'm off to listen to some music.</p>
<p>Marshall</p>
<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
<p>A WASD V2 87-key mechanical keyboard</p>
</div>
A Tale of Two Decentralized Protocols2020-04-13T00:00:00+00:002020-04-13T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0012-a-tail-of-two-decentralized-protocols/<p>Greetings, travelers.</p>
<p>This past weekend I got the chance to play with two different decentralized pieces of software. Consider the following some preliminary user reports on two technologies that I think have a lot of potential.</p>
<h2 id="tailscale">Tailscale</h2>
<p>The first is <a href="https://tailscale.com/">Tailscale</a>, a program/service that enables you to easily create a private network using <a href="https://www.wireguard.com/">WireGuard</a>.</p>
<p>Installing Tailscale was a breeze. Since I'm running NixOS, I was able to just tweak two lines in my <code>configuration.nix</code> to get it up and running. I imagine it is just as easy on other operating systems, although I have yet to try this.</p>
<p>A private network isn't very much fun with just one device, so I decided I'd throw my iPhone into the mix. Getting Tailscale installed on my iPhone was just as easy. All it took was installing the Tailscale app from the App Store and signing in, and now my iPhone was on the same private network as my desktop.</p>
<p>As a quick test I took my phone off the home WiFi and tried <code>ping</code>ing it from my desktop. And sure enough, the pings go through!</p>
<p>Great, so now I can <code>ping</code> my phone from my desktop. Big whoop. I tried to do something more interesting, like accessing a local webserver running on my desktop from my phone's browser. Sadly, I couldn't get this to work. I'm not sure if it's an issue with <code>localhost</code> specifically (although I did try running it on <code>0.0.0.0</code> instead of <code>127.0.0.1</code>), or if something else is the culprit.</p>
<p>Suffice to say, I haven't yet found an immediate use for Tailscale, but I am still excited by it nonetheless.</p>
<p>One thing that I think would be awesome is bringing back the era of LAN parties by getting a bunch of friends on a Tailscale network together. Maybe play some Warcraft 3 custom maps?</p>
<p>There's a great blog post on <a href="https://tailscale.com/blog/how-tailscale-works/">how Tailscale works</a> that I would encourage you to check out if you're interested in learning more.</p>
<h2 id="matrix-riot">Matrix / Riot</h2>
<p>The other program I started playing with is <a href="https://matrix.org/">Matrix</a>. Well, Matrix is more of a protocol than a program, so I didn't really play with it directly. Instead, I used <a href="https://about.riot.im/">Riot</a>—a chat app built on the Matrix protocol—as a means of interacting with Matrix.</p>
<p>The main feature of Matrix that I was interested in trying out was its ability to bridge to IRC networks. One of the main benefits of doing this is that Matrix acts as an IRC bouncer. So you can close your Matrix client, restart your computer, or even switch to Matrix client on another device and still stay connected to IRC.</p>
<p>I was able to bridge Matrix to Freenode and join some channels, but I ran into troubles when trying to get myself authorized with my Freenode account. It turns out I ran into a <a href="https://github.com/matrix-org/matrix-appservice-irc/issues/1006">bug</a> in the Matrix IRC bridge that halted my plans in their tracks. A fix is supposed to go out on Tuesday, at which point I'll attempt to get IRC hooked up again.</p>
<h2 id="new-blog-posts">New Blog Posts</h2>
<p>About a month ago I published "<a href="https://maxdeviant.com/posts/2020/a-primer-on-polymorphism/">A Primer on Polymorphism</a>", in which I showcase a number of different approaches to polymorphism and talk about the pros and cons of each.</p>
<p>The past week or two I've been emailing a lot more than usual, which prompted me to write a short post about "<a href="https://maxdeviant.com/posts/2020/the-good-side-of-email/">The Good Side of Email</a>".</p>
<h2 id="internet-findings">Internet Findings</h2>
<h3 id="discovering-python">Discovering Python</h3>
<p>This week I watched David Beazley's talk from PyCon 2014, "<a href="https://youtu.be/RZ4Sn-Y7AP8">Discovering Python</a>", in which we find our hero trapped in a vault with 1.5TB of C++ code and nought but a Python interpreter with which to analyze it. I found this talk to be highly interesting, amusing, and thought-provoking.</p>
<h3 id="the-danger-of-simplicity">The Danger of "Simplicity"</h3>
<p>I believe that simplicity is something that we developers should strive for in our work. However, there's a lot of misconceptions floating around about what "simple" really means. "<a href="https://asthasr.github.io/posts/danger-of-simplicity/">The Danger of 'Simplicity'</a>" explores this and seeks to bring to light the dangers of chasing "simplicity" rather than true simplicity.</p>
<p>That's all for now, folks!</p>
<p>Until next time,</p>
<p>Marshall</p>
The Good Side of Email2020-04-08T01:34:41.403+00:002020-04-08T01:34:41.403+00:00Unknownhttps://maxdeviant.com/posts/2020/the-good-side-of-email/<p>I hate email.</p>
<p>That last statement is only partially true. A more accurate rephrasing is that I hate email as a collaboration tool. I have been on enough forked email threads to know the nightmare that email can easily devolve into. For a while these negative experiences left a sour taste for email in my mouth.</p>
<p>Recently I've found myself emailing more than usual. These emails are only ever between me and one another person; a correspondence reminiscent of handwritten letters penned on stationery. If you've ever been to the stationery floor of <a href="https://www.loft.co.jp/">LOFT</a> you'll know that there must be some people out there who still dedicate themselves to the art of letter writing. While I'm not one for writing letters out by hand, I can still appreciate them.</p>
<p>While there is certainly an element to holding a physical letter in your hand and admiring—or grimacing at—the penmanship while you read over the words, the soul of a letter is in the written words themselves. It's this soul that is still capturable in an email.</p>
<p>Letters have a certain magic to them. They are a snapshot of someone's thoughts in a given point in time. Years can elapse, people can change, but their words are forever captured on that piece of paper.</p>
<p>In some ways an email is even more magical than a letter.</p>
<p>You can send an email to someone on the other side of the world in an instant. If they're in the habit of keeping owl-like hours you may even receive a reply equally as fast.</p>
<p>You can send an email to someone who you barely know, and as such would not want you knowing their home address.</p>
<p>Show me the good side of email and <a href="https://maxdeviant.com/open-invite/">email me</a>.</p>
<p>I love email.</p>
Windows Woes2020-04-04T00:00:00+00:002020-04-04T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0011-windows-woes/<p>Greetings, travelers.</p>
<p>I have quite the tale for you today, so grab a hot cup of tea (or some other beverage of your choosing) and settle in, because we're about to get right into it.</p>
<p>I recently purchased an <a href="https://www.newegg.com/samsung-970-evo-500gb/p/N82E16820147690">NVMe M.2 drive</a> for my desktop. It arrived earlier this week, so my plan for this weekend was to install Windows 10 on it and replace my twice-as-small drive that is currently running Windows.</p>
<p>Things did not go according to plan.</p>
<p>The first snag I encountered was that I did not have the proper screws to connect the M.2 drive to the motherboard. I didn't have the spare parts for my motherboard on hand, and the spare parts leftover from building my wife's computer also did not contain the screw I needed.</p>
<p>Not being one for giving up so easily, I made do with using a cable tie to hold the drive in place. For anyone screaming at me through your screen right now: don't worry, I ordered some proper screws and they'll be here on Tuesday.</p>
<p>With that little roadbump out of the way, I moved on to the process of installing Windows. Despite my best efforts, I still managed to fall victim to some of the dark patterns littered throughout the Windows 10 installer. Somehow I ended up installing Windows 10 using a Microsoft account instead of a local account. This (I believe) in turn forced me to set up a PIN for my user account. There was no way to skip this requirement. I continued to plod through the Windows 10 installer, trying desperately to avoid all of the extra crap that Microsoft tries to force upon you during the installation process.</p>
<p>Once the installation completed, I started configuring Windows 10. I installed my typical programs: Google Chrome, Nvidia drivers (yet another process fraught with blatant dark patterns), Discord, and some others. It was at some point in this process that I noticed something was wrong: my user folder name was incorrect.</p>
<p>For some unknown reason, my Windows user folder was named <code>ellio</code>. Not my preferred <code>maxdeviant</code>, or even <code>Marshall Bowers</code>; just <code>ellio</code>. No doubt this was a truncation of the email address attached to my Microsoft account, but the fact that it truncated after five characters is just totally bizarre.</p>
<p>Having noticed this flagrant misconfiguration I started taking steps to remedy it. Seeing no way of renaming the user folder from within the account management options, I fired up Command Prompt as Administrator and attempted to rename my user folder by hand. Permission denied. Even running with elevated privileges I couldn't fix the awful name Windows had chosen for my user folder.</p>
<p>At this point I was completely fed up with the whole affair. In this moment, I decided that Windows was not deserving of this lovely new NVMe M.2 drive and its glorious 1TB of space. No, this privilege belongs to the new favored child: <a href="https://nixos.org/">NixOS</a>.</p>
<p>With my mind made up, I plugged in my NixOS installation USB and said goodbye to Windows. One power cycle later and I was greeted by the welcome sight of the NixOS installer.</p>
<p>For the uninitiated, NixOS is a Linux distribution built on top of the Nix package manager. Its primary selling point is that it allows you to configure your entire system using declarative configuration files. These files can then be backed up somewhere, like GitHub, and then applied to a fresh NixOS installation to put it into the same state.</p>
<p>I've been using NixOS since November of last year so I already had my configuration files ready to go. It was time to see just how straightforward setting up a new system based on them would be. I followed the steps in the NixOS manual to partition and format the drive. When it came time to configure the system, I <code>curl</code>ed down my existing <code>configuration.nix</code> file from GitHub and used that in place of the default one. With my configuration file in place I let the installer run through.</p>
<p>Once the base system finished installing and booted into the new NixOS installation, I had a couple more things to do in order to get the system fully operational. Much of my NixOS configuration is performed by <a href="https://github.com/rycee/home-manager">Home Manager</a>, a project that allows you to manage individual user environments with Nix. It's basically like a <code>configuration.nix</code> but at the user level rather than the system level.</p>
<p>Restoring my user configuration with Home Manager was just as painless. The steps to do so can be found <a href="https://github.com/maxdeviant/dotfiles/tree/09c62f1df3fae387073e2298ee11f56caba8d936/hosts/firelink#installation">here</a>. With that done, my NixOS installation was complete, and almost indistinguishable from the old one.</p>
<p>It's kind of eerie, in a way. Normally a brand-new OS installation feels so fresh and barren, but this time around it felt like nothing had changed. To me, this is the value add of NixOS and declarative system configuration: the ability to take a fresh install and immediately restore it to exactly the way you want it.</p>
<p>I was incredibly pleased with how smoothly the installation process went. However, there's always room for improvement! To that end, I spent some of today whipping up an <a href="https://github.com/maxdeviant/dotfiles/blob/09c62f1df3fae387073e2298ee11f56caba8d936/nixos/install.sh">installation script</a> that I can use to get a fresh NixOS install up and running even faster in the future.</p>
<p>At some point during this crazy journey I had lost access to my existing Windows installation. Attempting to boot into it immediately showed an error stating that a device could not be found. As much as I would have loved to say "forget it", I still need access to Windows for work, so I was left with another mystery to solve.</p>
<p>It turns out that when I had tried installing Windows on the new drive, Windows had decided to install the bootloader on the old NixOS drive. Even once the new Windows install was replaced by NixOS the bootloader still remained, although now it was pointing to a non-existent Windows installation. Thus, attempting to boot could not possibly work.</p>
<p>After the botched Windows 10 install, I had disabled a number of SATA ports through UEFI in an attempt to keep Windows from installing its bootloader in places that it shouldn't. However, thanks to a very confusing UEFI interface, I had accidentally disabled the existing Windows drive, so the only Windows install that showed up in my GRUB bootloader was the non-existent one. Once I re-enabled the existing Windows drive in UEFI I was now able to boot back into Windows.</p>
<hr />
<p>As I'm sure you can tell from this story, my weekend has been nothing short of crazy so far. What started out as a presumably simple task ended with me being immeasurably frustrated with Windows and ready to wash my hands of it for good.</p>
<p>This whole ordeal has given me a lot to think about. I find the current state of the two mainstream operating systems—Windows 10 and macOS Catalina—incredibly worrisome. I see both of these operating systems moving in directions that I don't want to follow in.</p>
<p>While I'm not yet ready to completely ditch Windows or macOS, I intend to spend far less time with them. I will also start taking appropriate steps to distance myself from these platforms and more fully embrace NixOS.</p>
<p>If anyone is interested in NixOS, feel free to check out my <a href="https://github.com/maxdeviant/dotfiles">dotfiles</a> or shoot me an email.</p>
<p>Until next time,</p>
<p>Marshall</p>
Ownership and the Future2020-03-22T00:00:00+00:002020-03-22T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0010-ownership-and-the-future/<p>Hello again, fellow shut-ins.</p>
<p>I hope you are all, as I am, heeding the advice of medical professionals and practicing social distancing. As someone who already spends the majority of my non-working hours at home, this hasn't been quite as drastic a change as it has been for others.</p>
<p>One thing that I've been thinking about while cooped up at home is the idea of ownership.</p>
<p>At work I expend a great deal of time and energy towards cultivating an ecosystem in which to build software. Something that I have become increasingly aware of as of late is just how little ownership I have in the fruits of my labor. If I were to part ways with my employer I would be left to start over from nothing. I find the thought of this very troubling.</p>
<p>This weekend I wanted to work on a side project, but found myself faced with a dilemma: do I work on something that is either directly or closely related to my job, or do I start building a separate ecosystem that I have a greater level of ownership in? On the one hand, building libraries or tools that are applicable to my day job brings immediate benefits, as the majority of my time building software takes place at work. On the other hand, if I do this am I doing myself a disservice by not starting on laying the foundation for something that <em>I</em> want to build for myself?</p>
<p>In addition to this, there is also the future to consider. At work we predominantly use .NET, but I needed to take a step back and ask myself, "if this wasn't what we were already invested in at work, is it something that I would want to use for the next ten years?" Asking myself this question made me realize that, if I had the choice, I would not pick .NET as my technology of choice for building software.</p>
<p>Ultimately, I didn't end up getting much done this weekend aside from deeply considering these questions. So where does that leave me?</p>
<p>At this point I've more or less made my mind up that I want to spend my time outside of work cultivating my own ecosystem. I plan to use either Haskell, Rust, or a combination of the two as the foundation for this ecosystem. Both of these languages embody my ideals for building robust and maintainable software, they just go about achieving these goals in slightly different ways.</p>
<p>I'm curious if anyone else has struggled through these issues and if they would be willing to share their thoughts with me?</p>
<p>Be well, and you will hear from me again soon.</p>
<p>Marshall</p>
A Primer on Polymorphism2020-03-21T03:45:50.779+00:002020-03-21T03:45:50.779+00:00Unknownhttps://maxdeviant.com/posts/2020/a-primer-on-polymorphism/<p>Welcome to my primer on <a href="https://en.wikipedia.org/wiki/Polymorphism_(computer_science)">polymorphism</a>. By the end of this post the goal is for you to be able to effectively apply polymorphism in your own programs.</p>
<p>The examples in this post are written to satisfy a sample set of requirements. We'll take a look at a variety of different ways to satisfy these requirements.</p>
<p>Here are the requirements:</p>
<ol>
<li>We have three different kinds of animals: dogs, cats, and geese</li>
<li>All animals can make noise:
<ul>
<li>Dogs bark</li>
<li>Cats meow</li>
<li>Geese quack</li>
</ul>
</li>
<li>Geese can fly, but dogs and cats cannot</li>
</ol>
<h2 id="no-polymorphism">No Polymorphism</h2>
<p>The first approach that we'll cover is one where no polymorphism is used. This style of code is most common among beginner programmers.</p>
<p>Don't worry if your own code looks like this. It just means you stand to gain the most from reading this!</p>
<h3 id="modeling-the-animals">Modeling the animals</h3>
<p>We'll start by modeling the animals. Our requirements state that there are three different "kinds" of animals, so it makes sense to declare an <code>AnimalKind</code> enum to represent the various kinds.</p>
<p>We can then declare an <code>Animal</code> class containing a <code>Kind</code> property which can be used to differentiate between the various kinds of animals:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">enum </span><span>AnimalKind
</span><span style="color:#89ddff;">{
</span><span> Dog</span><span style="color:#89ddff;">,
</span><span> Cat</span><span style="color:#89ddff;">,
</span><span> Goose
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public </span><span style="color:#ffcb6b;">AnimalKind </span><span>Kind </span><span style="color:#89ddff;">{ </span><span style="font-style:italic;color:#c792ea;">get</span><span style="color:#89ddff;">; }
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="color:#82aaff;">Animal</span><span style="color:#89ddff;">(</span><span style="color:#ffcb6b;">AnimalKind </span><span style="color:#f78c6c;">kind</span><span style="color:#89ddff;">)
</span><span> </span><span style="color:#89ddff;">{
</span><span> Kind </span><span style="color:#89ddff;">= </span><span>kind</span><span style="color:#89ddff;">;
</span><span> </span><span style="color:#89ddff;">}
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>So far so good.</p>
<h3 id="make-some-noise">Make some noise</h3>
<p>Next we'll move on to the implementing the second requirement by giving our animals the ability to make noise:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">()
</span><span> </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">switch </span><span style="color:#89ddff;">(</span><span>Kind</span><span style="color:#89ddff;">)
</span><span> </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">case </span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Dog</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Bark</span><span style="color:#89ddff;">";
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">case </span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Cat</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Meow</span><span style="color:#89ddff;">";
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">case </span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Goose</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Honk</span><span style="color:#89ddff;">";
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">default</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">throw </span><span style="color:#89ddff;">new </span><span style="color:#ffcb6b;">ArgumentOutOfRangeException</span><span style="color:#89ddff;">(</span><span style="color:#c792ea;">nameof</span><span style="color:#89ddff;">(</span><span style="color:#ffcb6b;">Kind</span><span style="color:#89ddff;">));
</span><span> </span><span style="color:#89ddff;">}
</span><span> </span><span style="color:#89ddff;">}
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>While this works, we've already run into a bit of a rough edge by taking this approach. The compiler can't determine that we've handled all of the cases of <code>AnimalKind</code>, so we're forced to add a <code>default</code> case that throws an exception if we ever receive an animal that we're not expecting.</p>
<p>Under normal circumstances this could never happen, as <code>MakeNoise</code> handles every possible case, but if we were to add a fourth kind of animal—perhaps a <code>Pig</code>—down the road, we'd have to remember to update <code>MakeNoise</code> to handle the new case. Failing to handle the new case would result in an exception at runtime if <code>MakeNoise</code> is called for the new kind of animal.</p>
<p>It's also worth pointing out that the compiler will not provide any assistance if a new <code>AnimalKind</code> case is added. We're on our own to remember to add support for the new case everywhere.</p>
<h3 id="fly-away-home">Fly away home</h3>
<p>Finally we'll move on to implementing the third requirement of giving geese the ability to fly. However, we'll soon realize that we have a big problem:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">()
</span><span> </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">switch </span><span style="color:#89ddff;">(</span><span>Kind</span><span style="color:#89ddff;">)
</span><span> </span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">case </span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Goose</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">return </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose flies away!</span><span style="color:#89ddff;">";
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">case </span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Dog</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">case </span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Cat</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">throw </span><span style="color:#89ddff;">new </span><span style="color:#ffcb6b;">NotImplementedException</span><span style="color:#89ddff;">();
</span><span>
</span><span> </span><span style="font-style:italic;color:#c792ea;">default</span><span style="color:#89ddff;">:
</span><span> </span><span style="font-style:italic;color:#c792ea;">throw </span><span style="color:#89ddff;">new </span><span style="color:#ffcb6b;">ArgumentOutOfRangeException</span><span style="color:#89ddff;">(</span><span style="color:#c792ea;">nameof</span><span style="color:#89ddff;">(</span><span style="color:#ffcb6b;">Kind</span><span style="color:#89ddff;">));
</span><span> </span><span style="color:#89ddff;">}
</span><span> </span><span style="color:#89ddff;">}
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>It's easy enough to make <code>Fly</code> work for geese: we just make them fly. But now we're left with the question of what to do about animals that can't fly. In this case we've chosen to throw a <code>NotImplementedException</code> if a dog or cat tries to fly. We could throw a custom exception, like a <code>CannotFlyException</code>, but we're still left with the problem that someone could accidentally call <code>Fly</code> for a cat or dog and would only discover their mistake when the exception is thrown at runtime:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="font-style:italic;color:#c792ea;">var </span><span>goose </span><span style="color:#89ddff;">= new </span><span style="color:#ffcb6b;">Animal</span><span style="color:#89ddff;">(</span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Goose</span><span style="color:#89ddff;">);
</span><span>Console</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">WriteLine</span><span style="color:#89ddff;">(</span><span>goose</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">()); </span><span style="font-style:italic;color:#4a4a4a;">// "The goose flies away!"
</span><span>
</span><span style="font-style:italic;color:#c792ea;">var </span><span>dog </span><span style="color:#89ddff;">= new </span><span style="color:#ffcb6b;">Animal</span><span style="color:#89ddff;">(</span><span>AnimalKind</span><span style="color:#89ddff;">.</span><span>Dog</span><span style="color:#89ddff;">);
</span><span>Console</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">WriteLine</span><span style="color:#89ddff;">(</span><span>dog</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">()); </span><span style="font-style:italic;color:#4a4a4a;">// Oops, this throws an exception!
</span></code></pre>
<p>In the above code, the compiler will not provide any warnings or errors when <code>dog.Fly()</code> is called, even though we know that it's not valid.</p>
<h2 id="inheritance-based-polymorphism">Inheritance-based Polymorphism</h2>
<p>Now that we've seen the problems that a lack of polymorphism causes, let's try implementing this again, but this time taking an inheritance-based approach to polymorphism by using subclassing. After all, inheritance is what you learn in school, so it has to be good, right?</p>
<h3 id="modeling-the-animals-1">Modeling the animals</h3>
<p>This time around we'll create a base <code>Animal</code> class and declare three subclasses, one for each kind of animal, which all inherit from the base class:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public abstract </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Animal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Dog </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Cat </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Goose </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span></code></pre>
<h3 id="make-some-noise-1">Make some noise</h3>
<p>Just like before we can define a <code>MakeNoise</code> method on the base class. However, since we now have a class hierarchy we make the method abstract so that each of the subclasses can provide their own implementation:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public abstract </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public abstract </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">();
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Dog </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public override </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Bark</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Cat </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public override </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Meow</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Goose </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public override </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Honk</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>In contrast to our previous <code>MakeNoise</code> implementation, this time we actually receive some help from the compiler. When we first declare the abstract <code>MakeNoise</code> method on the <code>Animal</code> class, the compiler helpfully points out that we're missing an implementation for each of our subclasses:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>'Dog' does not implement inherited abstract member 'Animal.MakeNoise()'
</span><span>'Cat' does not implement inherited abstract member 'Animal.MakeNoise()'
</span><span>'Goose' does not implement inherited abstract member 'Animal.MakeNoise()'
</span></code></pre>
<h3 id="fly-away-home-1">Fly away home</h3>
<p>Implementing <code>Fly</code> works the same way. We declare an abstract <code>Fly</code> method on the base class and then implement it for each of the subclasses:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public abstract </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public abstract </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">();
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Dog </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public override </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#c792ea;">throw </span><span style="color:#89ddff;">new </span><span style="color:#ffcb6b;">NotImplementedException</span><span style="color:#89ddff;">();
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Cat </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public override </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#c792ea;">throw </span><span style="color:#89ddff;">new </span><span style="color:#ffcb6b;">NotImplementedException</span><span style="color:#89ddff;">();
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Goose </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">Animal
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public override </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose flies away!</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>Just like with <code>MakeNoise</code>, when we declare the abstract <code>Fly</code> method on the base class we get compiler warnings alerting us that we have to implement the method for each subclass.</p>
<p>Once again, we're faced with the same problem as before: only geese should be able to fly!</p>
<p>Additionally, we still have the issue where the compiler will not alert us if we try to call <code>Fly</code> on something that isn't a <code>Goose</code>:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="font-style:italic;color:#c792ea;">var </span><span>goose </span><span style="color:#89ddff;">= new </span><span style="color:#ffcb6b;">Goose</span><span style="color:#89ddff;">();
</span><span>Console</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">WriteLine</span><span style="color:#89ddff;">(</span><span>goose</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">()); </span><span style="font-style:italic;color:#4a4a4a;">// "The goose flies away!"
</span><span>
</span><span style="font-style:italic;color:#c792ea;">var </span><span>dog </span><span style="color:#89ddff;">= new </span><span style="color:#ffcb6b;">Dog</span><span style="color:#89ddff;">();
</span><span>Console</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">WriteLine</span><span style="color:#89ddff;">(</span><span>dog</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">()); </span><span style="font-style:italic;color:#4a4a4a;">// Oops, this throws an exception!
</span></code></pre>
<h2 id="composition-based-polymorphism">Composition-based Polymorphism</h2>
<p>At this point perhaps you've remembered that one should favor <a href="https://en.wikipedia.org/wiki/Composition_over_inheritance">composition over inheritance</a> when doing object-oriented programming. There are many reasons for this, and in this case eschewing inheritance is exactly what we need.</p>
<h3 id="modeling-the-animals-2">Modeling the animals</h3>
<p>This time around we'll use an <code>IAnimal</code> <a href="https://en.wikipedia.org/wiki/Marker_interface_pattern">marker interface</a> to indicate that something is an animal:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">interface </span><span>IAnimal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Dog </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Cat </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Goose </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span></code></pre>
<blockquote>
<p>The <code>IAnimal</code> interface could just as easily be an abstract class, but it doesn't make any difference in the context of this example.</p>
</blockquote>
<h3 id="make-some-noise-2">Make some noise</h3>
<p>In order to meet our noise-making requirements we'll declare a new <code>IMakeNoise</code> interface with our <code>MakeNoise</code> method on it. We can then implement this interface for each of our animals:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">interface </span><span>IMakeNoise
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">();
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Dog </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Bark</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Cat </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Meow</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Goose </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Honk</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span></code></pre>
<blockquote>
<p>Since all animals can make noise we could also tweak the above code to make <code>IAnimal</code> inherit from <code>IMakeNoise</code>:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">interface </span><span>IAnimal </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IMakeNoise
</span><span style="color:#89ddff;">{
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>Doing this would mean each of our animal classes would only have to implement <code>IAnimal</code> as opposed to both <code>IAnimal</code> and <code>IMakeNoise</code>.</p>
</blockquote>
<h3 id="fly-away-home-2">Fly away home</h3>
<p>So far we've had no success in implementing <code>Fly</code> in such a way that we're able to statically enforce that only geese can fly. Will it be any different this time around?</p>
<p>We'll declare a new <code>ICanFly</code> interface to represent animals that can fly. We can then implement this interface for <code>Goose</code> and only for <code>Goose</code>:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">interface </span><span>ICanFly
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">();
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Goose </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">ICanFly
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose flies away!</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>Let's see it in action:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="font-style:italic;color:#c792ea;">var </span><span>goose </span><span style="color:#89ddff;">= new </span><span style="color:#ffcb6b;">Goose</span><span style="color:#89ddff;">();
</span><span>Console</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">WriteLine</span><span style="color:#89ddff;">(</span><span>goose</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">()); </span><span style="font-style:italic;color:#4a4a4a;">// "The goose flies away!"
</span><span>
</span><span style="font-style:italic;color:#c792ea;">var </span><span>dog </span><span style="color:#89ddff;">= new </span><span style="color:#ffcb6b;">Dog</span><span style="color:#89ddff;">();
</span><span>Console</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">WriteLine</span><span style="color:#89ddff;">(</span><span>dog</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">()); </span><span style="font-style:italic;color:#4a4a4a;">// Does not compile!
</span></code></pre>
<p>Success! The compiler is now able to alert us when we try to call <code>Fly</code> on an animal that is unable to fly:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>'Dog' does not contain a definition for 'Fly' and no accessible extension method 'Fly' accepting a first argument of type 'Dog' could be found (are you missing a using directive or an assembly reference?)
</span></code></pre>
<p>It's important to note that this only works when we're dealing with a concrete animal class. If we're working with an <code>IAnimal</code> we need to perform <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/type-testing-and-cast">type-testing</a> at runtime in order to check if an animal implements <code>ICanFly</code>:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#ffcb6b;">IAnimal </span><span>dog </span><span style="color:#89ddff;">= new </span><span style="color:#ffcb6b;">Dog</span><span style="color:#89ddff;">();
</span><span style="font-style:italic;color:#c792ea;">if </span><span style="color:#89ddff;">(</span><span>dog </span><span style="color:#89ddff;">is </span><span style="color:#ffcb6b;">ICanFly </span><span>canFly</span><span style="color:#89ddff;">)
</span><span style="color:#89ddff;">{
</span><span> Console</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">WriteLine</span><span style="color:#89ddff;">(</span><span>canFly</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">());
</span><span style="color:#89ddff;">}
</span></code></pre>
<p>In the above snippet, nothing will be printed since <code>Dog</code> does not implement <code>ICanFly</code>.</p>
<h2 id="functional-polymorphism-in-f">Functional Polymorphism (in F#)</h2>
<p>Up until now we've only seen object-oriented approaches to polymorphism. Now we're going to switch gears and take a look at a functional approach to polymorphism using F#.</p>
<h3 id="modeling-the-animals-3">Modeling the animals</h3>
<p>In F# we can use a <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions">discriminated union</a>—also known as a "sum type"—to represent the various kinds of animals:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">type </span><span>Animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">|</span><span> Dog
</span><span> </span><span style="color:#c792ea;">|</span><span> Cat
</span><span> </span><span style="color:#c792ea;">|</span><span> Goose
</span></code></pre>
<p>If we wanted to put this into words we could say that "an animal can be either a dog <strong>or</strong> a cat <strong>or</strong> a goose".</p>
<h3 id="make-some-noise-3">Make some noise</h3>
<p>In functional programming it's good practice to separate the data from the behavior. This means that rather than creating a <code>MakeNoise</code> method on the <code>Animal</code> type itself we'll opt for a <code>makeNoise</code> function in an associated <code>Animal</code> module:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">module </span><span>Animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">let </span><span>makeNoise</span><span style="color:#f78c6c;"> animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> animal </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">|</span><span> Dog </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Bark</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Cat </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Meow</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Goose </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Honk</span><span style="color:#89ddff;">"
</span></code></pre>
<blockquote>
<p>A common functional programming pattern is to declare a module with the same name as the type to hold the functions that operate on that type.</p>
</blockquote>
<p>You may have noticed that our <code>makeNoise</code> implementation bears a striking resemblance to the one from our example without polymorphism. There is one key difference, though: the compiler will actually show us a warning if not all the cases are handled.</p>
<p>If we were to extend our <code>Animal</code> type with a new case for <code>Pig</code>, we'd see the following compiler warning in our <code>makeNoise</code> function:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>Incomplete pattern matches on this expression. For example, the value 'Pig' may indicate a case not covered by the pattern(s).
</span></code></pre>
<p>This is a welcome improvement! If new kinds of animals are added—either by us or another team member—the compiler will show us all the spots that need to account for the new cases. This compiler warning can even be promoted to an error, which means that the code <strong>will not compile</strong> unless all of the cases are accounted for.</p>
<h3 id="fly-away-home-3">Fly away home</h3>
<p>To implement our flying requirement we can add a <code>fly</code> function to <code>Animal</code> module:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">module </span><span>Animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">let </span><span>fly</span><span style="color:#f78c6c;"> animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> animal </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">|</span><span> Goose </span><span style="color:#c792ea;">-></span><span> Some </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose flies away!</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Dog
</span><span> </span><span style="color:#c792ea;">|</span><span> Cat </span><span style="color:#c792ea;">-></span><span> None
</span></code></pre>
<p>Now, this approach does contain the same problem we had in the first two approaches: we're able to call <code>fly</code> with any animal, not just ones that can fly. However, there is one small difference that makes this more acceptable.</p>
<p>In all of the other approaches the <code>Fly</code> method always returned a <code>string</code>. This time, however, the <code>fly</code> function returns a <code>string option</code>. Let's unpack this.</p>
<p>F# has an <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/options"><code>option</code></a> type that is used to represent a value that may or may not be there. The compiler then forces us to handle both cases when trying to access the value contained in <code>option</code>.</p>
<p>Whereas in the previous implementations we threw an exception when given an animal that cannot fly, this time around we return a <code>None</code> instead. What this means is that we now need to handle this case when we call <code>fly</code>:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">let </span><span>goose </span><span style="color:#c792ea;">=</span><span> Goose
</span><span style="color:#c792ea;">match</span><span> Animal.fly goose </span><span style="color:#c792ea;">with
</span><span style="color:#c792ea;">|</span><span> Some message </span><span style="color:#c792ea;">-></span><span> printfn </span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">%s</span><span style="color:#89ddff;">"</span><span> message </span><span style="font-style:italic;color:#4a4a4a;">// "The goose flies away!"
</span><span style="color:#c792ea;">|</span><span> None </span><span style="color:#c792ea;">-> </span><span style="color:#f78c6c;">()
</span><span>
</span><span style="color:#c792ea;">let </span><span>dog </span><span style="color:#c792ea;">=</span><span> Dog
</span><span style="color:#c792ea;">match</span><span> Animal.fly dog </span><span style="color:#c792ea;">with
</span><span style="color:#c792ea;">|</span><span> Some message </span><span style="color:#c792ea;">-></span><span> printfn </span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">%s</span><span style="color:#89ddff;">"</span><span> message
</span><span style="color:#c792ea;">|</span><span> None </span><span style="color:#c792ea;">->
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// Dogs can't fly, so we'll enter the `None` case
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// and do nothing.
</span><span> </span><span style="color:#f78c6c;">()
</span></code></pre>
<p>The above code compiles and runs successfully, without any exceptions being thrown. This is because when we handle the <code>None</code> case we simply do nothing (by returning <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/unit-type"><code>()</code></a>).</p>
<blockquote>
<p>We can also reduce the boilerplate of printing in the <code>Some</code> case and doing nothing in the <code>None</code> case, by moving it into its own function:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">let </span><span>flyAndPrint</span><span style="color:#f78c6c;"> animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> Animal.fly animal </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">|</span><span> Some message </span><span style="color:#c792ea;">-></span><span> printfn </span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">%s</span><span style="color:#89ddff;">"</span><span> message
</span><span> </span><span style="color:#c792ea;">|</span><span> None </span><span style="color:#c792ea;">-> </span><span style="color:#f78c6c;">()
</span><span>
</span><span style="color:#c792ea;">let </span><span>goose </span><span style="color:#c792ea;">=</span><span> Goose
</span><span>flyAndPrint goose </span><span style="font-style:italic;color:#4a4a4a;">// "The goose flies away!"
</span><span>
</span><span style="color:#c792ea;">let </span><span>dog </span><span style="color:#c792ea;">=</span><span> Dog
</span><span>flyAndPrint dog </span><span style="font-style:italic;color:#4a4a4a;">// Does nothing.
</span></code></pre>
</blockquote>
<p>At this point, what we have is functionally identical to the code from the composition example when working with an <code>IAnimal</code>.</p>
<p>One thing that the object-oriented compositional approach gives us that we don't have here is compile-time type checking when working with concrete instances like <code>Dog</code> or <code>Goose</code>.</p>
<p>Unfortunately, achieving this same behavior in a functional paradigm requires the use of <a href="https://en.wikipedia.org/wiki/Type_class">type classes</a>, which F# does not have. With that said, we've done all we can do for now.</p>
<blockquote>
<p>It is probably possible to make use of <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/statically-resolved-type-parameters">statically resolved type parameters (SRTPs)</a> in F# to work around the lack of type classes, but this is left as an exercise for the reader.</p>
</blockquote>
<h2 id="functional-polymorphism-in-haskell">Functional Polymorphism (in Haskell)</h2>
<p>This section will explore how to implement the object-oriented composition example in a functional paradigm using Haskell.</p>
<p>This isn't required reading for the rest of the post, so if you're not interested in learning about Haskell you can skip ahead to <a href="https://maxdeviant.com/posts/2020/a-primer-on-polymorphism/#the-expression-problem">the next section</a>.</p>
<h3 id="modeling-the-animals-4">Modeling the animals</h3>
<p>We can model the animals using standard Haskell data types. We declare a data type for each individual animal, as well as an <code>Animal</code> type that encompasses all of the kinds of animals:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">data </span><span>Animal
</span><span> </span><span style="color:#89ddff;">= </span><span>Dog' Dog
</span><span> </span><span style="color:#89ddff;">| </span><span>Cat' Cat
</span><span> </span><span style="color:#89ddff;">| </span><span>Goose' Goose
</span><span> </span><span style="color:#c792ea;">deriving</span><span> (</span><span style="color:#c3e88d;">Show</span><span>)
</span><span>
</span><span style="color:#c792ea;">data </span><span>Dog </span><span style="color:#89ddff;">=
</span><span> Dog
</span><span> </span><span style="color:#c792ea;">deriving</span><span> (</span><span style="color:#c3e88d;">Show</span><span>)
</span><span>
</span><span style="color:#c792ea;">data </span><span>Cat </span><span style="color:#89ddff;">=
</span><span> Cat
</span><span> </span><span style="color:#c792ea;">deriving</span><span> (</span><span style="color:#c3e88d;">Show</span><span>)
</span><span>
</span><span style="color:#c792ea;">data </span><span>Goose </span><span style="color:#89ddff;">=
</span><span> Goose
</span><span> </span><span style="color:#c792ea;">deriving</span><span> (</span><span style="color:#c3e88d;">Show</span><span>)
</span></code></pre>
<h3 id="make-some-noise-4">Make some noise</h3>
<p>To implement <code>makeNoise</code> we first declare a <code>MakeNoise</code> type class and then create an instance of it for each of our animals:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">class </span><span style="color:#c3e88d;">MakeNoise </span><span>a </span><span style="color:#c792ea;">where
</span><span> </span><span style="color:#82aaff;">makeNoise </span><span style="color:#c792ea;">:: </span><span>a </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>
</span><span style="color:#c792ea;">instance </span><span style="font-style:italic;color:#c792ea;">MakeNoise Dog </span><span style="color:#c792ea;">where
</span><span> makeNoise _ </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">Bark</span><span style="color:#89ddff;">"
</span><span>
</span><span style="color:#c792ea;">instance </span><span style="font-style:italic;color:#c792ea;">MakeNoise Cat </span><span style="color:#c792ea;">where
</span><span> makeNoise _ </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">Meow</span><span style="color:#89ddff;">"
</span><span>
</span><span style="color:#c792ea;">instance </span><span style="font-style:italic;color:#c792ea;">MakeNoise Goose </span><span style="color:#c792ea;">where
</span><span> makeNoise _ </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">Honk</span><span style="color:#89ddff;">"
</span></code></pre>
<h3 id="fly-away-home-4">Fly away home</h3>
<p>We can implement flying the same way. We declare a <code>CanFly</code> type class, but this time we only create an instance for our <code>Goose</code> type:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">class </span><span style="color:#c3e88d;">CanFly </span><span>a </span><span style="color:#c792ea;">where
</span><span> </span><span style="color:#82aaff;">fly </span><span style="color:#c792ea;">:: </span><span>a </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>
</span><span style="color:#c792ea;">instance </span><span style="font-style:italic;color:#c792ea;">CanFly Goose </span><span style="color:#c792ea;">where
</span><span> fly _ </span><span style="color:#89ddff;">= "</span><span style="color:#c3e88d;">The goose flies away!</span><span style="color:#89ddff;">"
</span></code></pre>
<p>If we test this out in GHCi we'll see that trying to call <code>fly</code> on an instance of <code>Dog</code> results in a compiler error:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> goose </span><span style="color:#89ddff;">= </span><span>Goose
</span><span>ghci</span><span style="color:#89ddff;">></span><span> fly goose
</span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose flies away!</span><span style="color:#89ddff;">"
</span><span>
</span><span>ghci</span><span style="color:#89ddff;">></span><span> dog </span><span style="color:#89ddff;">= </span><span>Dog
</span><span>ghci</span><span style="color:#89ddff;">></span><span> fly dog
</span><span style="color:#89ddff;"><</span><span>interactive</span><span style="color:#89ddff;">>:</span><span style="color:#f78c6c;">6</span><span style="color:#89ddff;">:</span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">:</span><span> error</span><span style="color:#89ddff;">:
</span><span> • No </span><span style="color:#c792ea;">instance </span><span>for (</span><span style="font-style:italic;color:#c792ea;">CanFly Dog</span><span>) arising from a use of ‘fly’
</span><span> • In the expression</span><span style="color:#89ddff;">:</span><span> fly dog
</span><span> In an equation for ‘it’</span><span style="color:#89ddff;">:</span><span> it </span><span style="color:#89ddff;">=</span><span> fly dog
</span></code></pre>
<h2 id="the-expression-problem">The Expression Problem</h2>
<p>Having seen an object-oriented approach and a functional approach to writing a program that satisfies our outlined requirements we're left with a question: "which one should I pick?"</p>
<p>Answering this question requires an understanding of <a href="https://en.wikipedia.org/wiki/Expression_problem">the expression problem</a>, which is best summed up by the following table:</p>
<table><thead><tr><th>Paradigm</th><th>Adding new types</th><th>Adding new behavior</th></tr></thead><tbody>
<tr><td>Object-oriented</td><td>Easy</td><td>Difficult</td></tr>
<tr><td>Functional</td><td>Difficult</td><td>Easy</td></tr>
</tbody></table>
<p>In the object-oriented paradigm it is easy to add new types, but difficult to add new behavior.</p>
<p>In the functional paradigm it is easy to add new behavior, but difficult to add new types.</p>
<blockquote>
<p>In this context "easy" means "does not require changing and recompiling existing code" and "difficult" means "requires changing and recompiling existing code".</p>
</blockquote>
<h3 id="example-object-oriented-paradigm">Example: object-oriented paradigm</h3>
<h4 id="adding-a-new-type">Adding a new type</h4>
<p>Adding a new type is easy, as we just need to define a new <code>Pig</code> class that implements <code>IAnimal</code> and any of our other interfaces:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Pig </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">ICanFly
</span><span style="color:#89ddff;">{
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">MakeNoise</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Oink</span><span style="color:#89ddff;">";
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Fly</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Yea right, when pigs fly!</span><span style="color:#89ddff;">"
</span><span style="color:#89ddff;">}
</span></code></pre>
<h4 id="adding-new-behavior">Adding new behavior</h4>
<p>Adding new behavior is difficult, as we have to implement <code>ICanSwim</code> for any existing types that require it:</p>
<pre data-lang="cs" style="background-color:#212121;color:#eeffff;" class="language-cs "><code class="language-cs" data-lang="cs"><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">interface </span><span>ICanSwim
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Swim</span><span style="color:#89ddff;">();
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Dog </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">ICanSwim
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Swim</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The dog happily doggy paddles in the water.</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Cat </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">ICanSwim
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Swim</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The cat flails about in the water.</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span><span>
</span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">class </span><span style="color:#ffcb6b;">Goose </span><span style="color:#89ddff;">: </span><span style="color:#c3e88d;">IAnimal</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">IMakeNoise</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">ICanFly</span><span style="color:#89ddff;">, </span><span style="color:#c3e88d;">ICanSwim
</span><span style="color:#89ddff;">{
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">// ...
</span><span>
</span><span> </span><span style="color:#c792ea;">public </span><span style="font-style:italic;color:#c792ea;">string </span><span style="color:#82aaff;">Swim</span><span style="color:#89ddff;">() </span><span style="font-style:italic;color:#c792ea;">=> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose calmly floats on the water.</span><span style="color:#89ddff;">";
</span><span style="color:#89ddff;">}
</span></code></pre>
<h3 id="example-functional-paradigm">Example: functional paradigm</h3>
<h4 id="adding-a-new-type-1">Adding a new type</h4>
<p>Adding a new type is difficult, as we have to add a new case to <code>Animal</code> and update any code using <code>Animal</code> to handle the new case:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">type </span><span>Animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">|</span><span> Dog
</span><span> </span><span style="color:#c792ea;">|</span><span> Cat
</span><span> </span><span style="color:#c792ea;">|</span><span> Goose
</span><span> </span><span style="color:#c792ea;">|</span><span> Pig
</span><span>
</span><span style="color:#c792ea;">module </span><span>Animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">let </span><span>makeNoise</span><span style="color:#f78c6c;"> animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> animal </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">|</span><span> Dog </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Bark</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Cat </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Meow</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Goose </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Honk</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Pig </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Oink</span><span style="color:#89ddff;">"
</span><span>
</span><span> </span><span style="color:#c792ea;">let </span><span>fly</span><span style="color:#f78c6c;"> animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> animal </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">|</span><span> Goose </span><span style="color:#c792ea;">-></span><span> Some </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose flies away!</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Pig </span><span style="color:#c792ea;">-></span><span> Some </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Yea right, when pigs fly!</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Dog
</span><span> </span><span style="color:#c792ea;">|</span><span> Cat </span><span style="color:#c792ea;">-></span><span> None
</span></code></pre>
<h4 id="adding-new-behavior-1">Adding new behavior</h4>
<p>Adding new behavior is easy, as we can just define a new function that operates on the existing types:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">let </span><span>swim</span><span style="color:#f78c6c;"> animal </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> animal </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">|</span><span> Dog </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The dog happily doggy paddles in the water.</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Cat </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The cat flails about in the water.</span><span style="color:#89ddff;">"
</span><span> </span><span style="color:#c792ea;">|</span><span> Goose </span><span style="color:#c792ea;">-> </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">The goose calmly floats on the water.</span><span style="color:#89ddff;">"
</span></code></pre>
<h3 id="which-one-should-i-pick">"Which one should I pick?"</h3>
<p>It depends on the environment in which the code resides.</p>
<p>Personally, I have found that most of the applications I've worked on have a core set of types that change infrequently, but are always in need of having new behaviors added to them.</p>
<p>This observation causes me to lean towards the functional paradigm, as it lends itself to easily adding new behavior to existing types.</p>
<h2 id="closing-remarks">Closing Remarks</h2>
<p>We've covered a lot of ground in this post, and I hope you've come out the other side with a better understanding of polymorphism and how to apply it.</p>
<p>The compositional object-oriented approach and the functional approaches are the two that I would suggest following, depending on what paradigm you are using. Inheritance—especially in the form of deep inheritance hierarchies—is a well-known antipattern and should be avoided.</p>
<hr />
<p>If you would like to play around with the code examples, you can find them <a href="https://github.com/maxdeviant/polymorphism-primer">on GitHub</a>.</p>
<p>As always, if you have questions, comments, or other feedback, I'd love to <a href="https://maxdeviant.com/open-invite/">hear it</a>.</p>
Why I Dislike VB.NET2020-02-01T02:30:45.467+00:002020-02-01T02:30:45.467+00:00Unknownhttps://maxdeviant.com/posts/2020/why-i-dislike-vb-net/<p>I have used <a href="https://en.wikipedia.org/wiki/Visual_Basic_.NET">Visual Basic .NET</a> (VB.NET) at work for the past six years. I also strongly dislike it as a language. During this period as my software development skills and taste in language design have developed, so has my utter distaste for VB.NET.</p>
<p>For a while now I have been meaning to compile my thoughts on the subject so that when someone asks me <em>why</em> I don't like VB.NET I can point them somewhere that explains, in detail, the issues I have with the language.</p>
<p>The factors that have influenced by dislike for VB.NET range from major problems with the language to minor annoyances that have driven me crazy over time. While I try to maintain a somewhat objective position, there are definitely some factors that are purely my own opinion.</p>
<p>With all that said, let's get into it.</p>
<h2 id="lax-compiler-defaults">Lax Compiler Defaults</h2>
<p>The VB.NET compiler has four different compiler options: <code>Option Compare</code>, <code>Option Explicit</code>, <code>Option Infer</code>, and <code>Option Strict</code>. Of these four, the latter three should all be set to <code>On</code> in order to get the most safety and utility out of the compiler.</p>
<p>While <code>Option Explicit</code> and <code>Option Infer</code> both default to <code>On</code>, <code>Option Strict</code> does not.</p>
<p>According to the docs:</p>
<blockquote>
<p>[<code>Option Strict</code>] [r]estricts implicit data type conversions to only widening conversions, disallows late binding, and disallows implicit typing that results in an <code>Object</code> type.</p>
</blockquote>
<p>That may sound like a lot of jargon, so let's take a closer look at why the default of <code>Off</code> for <code>Option Strict</code> is a bad choice.</p>
<h3 id="exhibit-a">Exhibit A</h3>
<p>Here we have a simple program:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>Imports System
</span><span>Imports System.Collections.Generic
</span><span>Imports Newtonsoft.Json
</span><span>
</span><span>Module Program
</span><span> <JsonObject>
</span><span> Class ItemsContainer
</span><span> Public Property Items As List(Of Integer)
</span><span> End Class
</span><span>
</span><span> Sub Main(args As String())
</span><span> Dim items As New List(Of Integer) From {1, 2, 3, 4, 5}
</span><span>
</span><span> Dim cart As New ItemsContainer With {
</span><span> .Items = items.Select(Function(n) n + 1)
</span><span> }
</span><span>
</span><span> Console.WriteLine(JsonConvert.SerializeObject(cart))
</span><span> End Sub
</span><span>End Module
</span></code></pre>
<p>This program will compile with no errors or warnings. However, running this program will result in you being greeted with a lovely <code>System.InvalidCastException</code>:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>Unhandled exception. System.InvalidCastException: Unable to cast object of type 'SelectListIterator`2[System.Int32,System.Int32]' to type 'System.Collections.Generic.List`1[System.Int32]'.
</span><span> at Sandbox.Program.Main(String[] args) in /Sandbox/Program.vb:line 14
</span></code></pre>
<p>If we modify the project options and set <code>Option Strict</code> to <code>On</code> we get a very different result at compile time:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>/Sandbox/Program.vb(15,22): error BC30512: Option Strict On disallows implicit conversions from 'IEnumerable(Of Integer)' to 'List(Of Integer)'.
</span></code></pre>
<p>Now that the compiler has alerted us to this mistake, fixing it is just a matter of adding a <code>.ToList</code> to materialize the list:</p>
<pre data-lang="diff" style="background-color:#212121;color:#eeffff;" class="language-diff "><code class="language-diff" data-lang="diff"><span>Dim cart As New ItemsContainer With {
</span><span style="color:#89ddff;">-</span><span style="color:#ff5370;"> .Items = items.Select(Function(n) n + 1)
</span><span style="color:#89ddff;">+</span><span style="color:#c3e88d;"> .Items = items.Select(Function(n) n + 1).ToList
</span><span>}
</span></code></pre>
<h3 id="exhibit-b">Exhibit B</h3>
<p>Let's turn <code>Option Strict</code> back off and take a look at another example:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>Imports System
</span><span>
</span><span>Module Program
</span><span> Class Employee
</span><span> Public Property IdNumber As Integer
</span><span> End Class
</span><span>
</span><span> Class Customer
</span><span> Public Property CustomerNumber As Integer
</span><span> End Class
</span><span>
</span><span> Sub Main(args As String())
</span><span> Dim employeeOfTheMonth = GetEmployeeOfTheMonth()
</span><span> Pay(employeeOfTheMonth)
</span><span> End Sub
</span><span>
</span><span> Function GetEmployeeOfTheMonth()
</span><span> Dim alice As New Employee With {
</span><span> .IdNumber = 64812
</span><span> }
</span><span>
</span><span> Dim bob As New Customer With {
</span><span> .CustomerNumber = 97832
</span><span> }
</span><span>
</span><span> Return bob
</span><span> End Function
</span><span>
</span><span> Sub Pay(employee As Employee)
</span><span> Console.WriteLine($"Paid employee #{employee.IdNumber}.")
</span><span> End Sub
</span><span>End Module
</span></code></pre>
<p>Did you notice the problem with this code snippet? If you didn't, don't feel bad; the compiler didn't catch this one either. If we build our program, we get no errors or warnings from the compiler.</p>
<p>Let's try running our program:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>Unhandled exception. System.InvalidCastException: Unable to cast object of type 'Customer' to type 'Employee'.
</span><span> at Sandbox.Program.Main(String[] args) in /Sandbox/Program.vb:line 15
</span></code></pre>
<p>Another <code>System.InvalidCastException</code>!</p>
<p>Let's turn <code>Option Strict</code> back on and see if the compiler could have saved us here:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>/Sandbox/Program.vb(17,14): error BC30210: Option Strict On requires all Function, Prope
</span><span>rty, and Operator declarations to have an 'As' clause.
</span></code></pre>
<p>The first thing our now-enlightened compiler points out is that we are missing a return type on our <code>GetEmployeeOfTheMonth</code> function:</p>
<pre data-lang="diff" style="background-color:#212121;color:#eeffff;" class="language-diff "><code class="language-diff" data-lang="diff"><span style="color:#89ddff;">-</span><span style="color:#ff5370;">Function GetEmployeeOfTheMonth()
</span><span style="color:#89ddff;">+</span><span style="color:#c3e88d;">Function GetEmployeeOfTheMonth() As Employee
</span><span> Dim alice As New Employee With {
</span><span> .IdNumber = 64812
</span><span> }
</span><span>
</span><span> Dim bob As New Customer With {
</span><span> .CustomerNumber = 97832
</span><span> }
</span><span>
</span><span> Return bob
</span></code></pre>
<p><code>GetEmployeeOfTheMonth</code> should clearly return an <code>Employee</code>.</p>
<p>If we build again we'll see another error:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>/Sandbox/Program.vb(26,16): error BC30311: Value of type 'Program.Customer' cannot be converted to 'Program.Employee'.
</span></code></pre>
<p>We've now determined the root cause of our troubles: we were trying to return a customer as the employee of the month!</p>
<p>One small tweak and we'll make sure that our employee, Alice, gets the compensation she deserves:</p>
<pre data-lang="diff" style="background-color:#212121;color:#eeffff;" class="language-diff "><code class="language-diff" data-lang="diff"><span>Function GetEmployeeOfTheMonth() As Employee
</span><span> Dim alice As New Employee With {
</span><span> .IdNumber = 64812
</span><span> }
</span><span>
</span><span> Dim bob As New Customer With {
</span><span> .CustomerNumber = 97832
</span><span> }
</span><span>
</span><span style="color:#89ddff;">-</span><span style="color:#ff5370;"> Return bob
</span><span style="color:#89ddff;">+</span><span style="color:#c3e88d;"> Return alice
</span><span>End Function
</span></code></pre>
<p>If we run the program now we'll see that we correctly paid our employee of the month:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>Paid employee #64812.
</span></code></pre>
<h2 id="the-microsoft-visualbasic-namespace">The Microsoft.VisualBasic Namespace</h2>
<blockquote>
<p>The <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic?view=netframework-4.8">Microsoft.VisualBasic</a> namespace contains types that support the Visual Basic Runtime in Visual Basic.</p>
</blockquote>
<p>While undoubtedly useful as a tool for transitioning legacy VB6 programs to VB.NET, the usefulness of the <code>Microsoft.VisualBasic</code> namespace stops there.</p>
<p>Its existence serves as a crutch for those who are used to programming in VB6 and prevents the adoption of common .NET idioms. One good example of this is <code>Microsoft.VisualBasic</code>'s <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.tristate?view=netframework-4.8"><code>TriState</code> enum</a>. This type is used to indicate <code>True</code>, <code>False</code>, or the absence of a value <code>UseDefault</code>. This type, however, is entirely redundant with <code>Boolean?</code> (aka <code>Nullable(Of Boolean)</code>) that exists within .NET itself.</p>
<p>This namespace is also imported by default for .NET Framework projects. This is especially problematic, as it means that developers may unwittingly start relying it.</p>
<p>One other major problem is that the <code>Microsoft.VisualBasic</code> namespace only exists on .NET Framework, which means that any VB.NET programs that rely on it cannot be ported to .NET Standard or .NET Core without first removing all usages of members from this namespace.</p>
<h2 id="functional-programming-woes">Functional Programming Woes</h2>
<p>Trying to write VB.NET code in a functional style is an exercise wrought with pain. While possible, the end result is incredibly verbose and far uglier than what could be achieved in F# or even C#.</p>
<h3 id="addressof">AddressOf</h3>
<p>When passing <a href="https://en.wikipedia.org/wiki/First-class_function#Higher-order_functions:_passing_functions_as_arguments">functions as arguments</a> VB.NET forces you to use the <code>AddressOf</code> operator, which is needlessly verbose:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>Imports System
</span><span>Imports System.Linq
</span><span>
</span><span>Module Program
</span><span> Sub Main(args As String())
</span><span> Dim numbers As New List(Of Integer) From {1, 2, 3, 4, 5}
</span><span> Dim factorial = numbers.Aggregate(1, AddressOf Multiply)
</span><span>
</span><span> Console.WriteLine(factorial)
</span><span> End Sub
</span><span>
</span><span> Function Multiply(a As Integer, b As Integer) As Integer
</span><span> Return a * b
</span><span> End Function
</span><span>End Module
</span></code></pre>
<h3 id="bad-lambdas">Bad Lambdas</h3>
<p>VB.NET's lambdas are also far too verbose. The keywords themselves are far too long, especially when dealing with lambda-heavy APIs like LINQ.</p>
<p>Visual Studio also has this annoying habit of lining up lambdas like so:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>Imports System
</span><span>
</span><span>Module Program
</span><span> Sub Main(args As String())
</span><span> Dim message = AFunctionWithAReallyLongNameToProveAPoint(Function()
</span><span> Return "Hello, world"
</span><span> End Function)
</span><span> Console.WriteLine(message)
</span><span> End Sub
</span><span>
</span><span> Function AFunctionWithAReallyLongNameToProveAPoint(f As Func(Of String)) As String
</span><span> Return f()
</span><span> End Function
</span><span>End Module
</span></code></pre>
<p>This usually results in the more interesting parts of the code getting lost on the right side of the screen.</p>
<h2 id="separators">Separators</h2>
<p>In VB.NET the separator character (<code>:</code>) exists to put multiple statements on a single line, like so:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>a = 3.2 : b = 7.6 : c = 2
</span></code></pre>
<p>Personally, I don't see why you'd want to do this. Having the statements on separate lines would be easier to read as well as play nicer with version control.</p>
<p>The real problem I have with separators is when they make their way into control flow:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>If True Then Console.WriteLine("Foo") : If False Then Console.WriteLine("Bar") : Else Console.WriteLine("Baz")
</span></code></pre>
<p>What would this piece of code output if we were to run it?</p>
<p>The answer is:</p>
<pre style="background-color:#212121;color:#eeffff;"><code><span>Foo
</span><span>Baz
</span></code></pre>
<p>Regardless of how trivial it might have been to figure out what the output would be, I maintain that it would be much easier were that code written like this:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>If True Then
</span><span> Console.WriteLine("Foo")
</span><span> If False Then
</span><span> Console.WriteLine("Bar")
</span><span> Else
</span><span> Console.WriteLine("Baz")
</span><span> End If
</span><span>End If
</span></code></pre>
<p>The second example makes it blatantly obvious what the result of running this code would be.</p>
<h2 id="byval">ByVal</h2>
<p>The <code>ByVal</code> keyword exists to indicate that an argument is passed <a href="https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/passing-arguments-by-value-and-by-reference">by value</a>.</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>Sub Greet(ByVal name As String)
</span><span> Console.WriteLine($"Hello there, {name}")
</span><span>End Sub
</span></code></pre>
<p>By default, all arguments in VB.NET are passed by value, so we can just drop the <code>ByVal</code> entirely:</p>
<pre data-lang="vb" style="background-color:#212121;color:#eeffff;" class="language-vb "><code class="language-vb" data-lang="vb"><span>Sub Greet(name As String)
</span><span> Console.WriteLine($"Hello there, {name}")
</span><span>End Sub
</span></code></pre>
<p>The official <a href="https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/modifiers/byval">guidance</a> from the VB.NET docs is to not use <code>ByVal</code>:</p>
<blockquote>
<p>Because it is the default, you do not have to explicitly specify the <code>ByVal</code> keyword in method signatures. It tends to produce noisy code and often leads to the non-default <code>ByRef</code> keyword being overlooked.</p>
</blockquote>
<p>This begs the question: why even bother having <code>ByVal</code> in the first place?</p>
<p>I suspect this is yet another wart inherited from VB6. In VB6 every argument is <code>ByRef</code> by default with <code>ByVal</code> being opt-in behavior: the exact opposite of how it works in VB.NET.</p>
<!-- prettier-ignore -->
<h2 id="it-s-not-f">It's Not F#</h2>
<p>At the end of the day, the biggest reason I dislike VB.NET is because if I'm writing VB.NET it means I could be writing F# instead. Given that both languages run on the .NET platform and can interoperate with other .NET languages, F# can be used anywhere that VB.NET can.</p>
<p>The bottom line is that F# is my go-to choice for solving any problem on .NET, and if there is a particular area where F# doesn't quite fit then I'll use C# to plug in the gaps.</p>
May All Your Monoliths Be Majestic2020-01-24T00:00:00+00:002020-01-24T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0008-may-all-your-monoliths-be-majestic/<p>Hello there, travelers.</p>
<p>I wanted to tell you a bit about a yet-unannounced side project that I started work on this week. Specifically, I wanted to talk about some of the architectural decisions that I made and what the outcome of those decisions has been so far.</p>
<p>For this project I've decided to take the approach of building a <a href="https://m.signalvnoise.com/the-majestic-monolith/">majestic monolith</a>. The term "monolith" has a rather negative connotation surrounding it in the software space. When someone hears "monolith" they will most likely immediately picture a <a href="http://www.laputan.org/mud/">Big Ball of Mud</a>: a software system that is "a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle".</p>
<p>While that sort of system is a monolith, it is not of the majestic kind. It is entirely possible to build a monolithic application that is still loosely-coupled, well-factored, and modularized without losing the simplicity and velocity that having a single deployment unit can provide.</p>
<p>Here's a glimpse at what the stack looks like so far:</p>
<ul>
<li><a href="https://www.rust-lang.org/">Rust</a> as my language of choice</li>
<li><a href="https://rocket.rs/">Rocket</a> to provide the web framework foundation</li>
<li><a href="https://tera.netlify.com/">Tera</a> for view templating</li>
<li><a href="http://diesel.rs/">Diesel</a> for interacting with the database (Postgres)</li>
</ul>
<p>One of the things that I've immediately noticed about working with this particular stack is just how easy it is to crank out new features. Most of my previous projects, as well as my projects at work, have maintained a separation between the backend and the frontend. The backend would be a REST API that the frontend—usually a React app—would consume. While this is a nice architecture, especially for larger teams, it suffers from being entirely too much of a burden for small team, especially when "small" is code for "just me".</p>
<p>There are still some more additions to the stack in store; I still haven't figured out the stories for styling or providing progressive-enhancement with JavaScript will be yet. And I'm sure that I will be talking more about this project in coming newsletters.</p>
<p>For now, I will leave you with some cool findings from the internet:</p>
<ul>
<li><a href="https://fasterthanli.me/blog/2019/declarative-memory-management/">Declarative memory management</a>: This is a lengthy (but well-worth it) blog post about the way Rust manages memory. It's a super fun read and I would highly recommend, especially if you're not super familiar with Rust.</li>
<li><a href="https://www.scotthyoung.com/blog/2020/01/13/too-tired/">Too Tired to Do Everything? How to Live Better Without Burning Out</a>: For those of you who are feeling burnt out (like I currently am), I hope you find this helpful.</li>
</ul>
<p>Until next time,</p>
<p>Marshall</p>
<p>P.S. It has come to my attention that at least one of my subscribers was getting warnings about my newsletter from their email provider. I am going to get a proper domain setup for my newsletter to hopefully address this issue.</p>
Momentum2020-01-24T00:00:00+00:002020-01-24T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0009-momentum/<p>Hello again, travelers.</p>
<p>It's been far too long since my last communiqué to all of you. Despite my best intentions, life has a nasty habit of getting in the way.</p>
<p>One thing that I observed over the course of this hiatus is how each passing week made it harder to sit down and write the next issue of Errata Exist. When it comes to forming and practicing habits I often place a lot of focus on building momentum that will help carry me through the times where I don't feel like doing something. I tend to think of it as being a matter of either having momentum or not having it. But I think that it is equally, if not more, important to recognize the power of negative momentum. While positive momentum can carry you through a rut, negative momentum will drag you deeper and deeper down into the rut.</p>
<p>Despite my shortcomings, I am optimistic about and very much looking forward to the future of this newsletter. I've already had a handful of people reach out to me to ask questions or just to say hello. I cherish these interactions and hope to have many more of them in the future.</p>
<h2 id="new-blog-post">New Blog Post</h2>
<p>At the end of last month I published "<a href="https://maxdeviant.com/posts/2020/why-i-dislike-vb-net/">Why I Dislike VB.NET</a>", in which I unpack a—probably inexhaustive—list of the things I don't like in VB.NET. This subject has been floating around in the back of my brain for a while now, but it wasn't until I had a Twitter conversation about VB.NET recently that I decided to go ahead and write it.</p>
<h2 id="cool-findings">Cool Findings</h2>
<h3 id="vgtk">vgtk</h3>
<p><a href="https://github.com/bodil/vgtk">vgtk</a> is a declarative desktop UI framework for Rust built on <a href="https://www.gtk.org/">GTK</a> and <a href="https://gtk-rs.org/"><code>gtk-rs</code></a>. I have been excited about building declarative UIs in Rust every since I saw <a href="https://twitter.com/bodil/status/1055921918413553664?s=20">this tweet</a> showcasing JSX-like syntax in Rust.</p>
<p>vgtk expands on that concept and brings a JSX/React-like syntax to Rust. I played around with it a bit this weekend and was very happy with how quickly I was able to start building a UI using it, especially having never used GTK before.</p>
<h3 id="escape-from-the-ivory-tower">Escape from the Ivory Tower</h3>
<p>I recently watched <a href="https://youtu.be/re96UgMk6GQ">this talk</a> by Simon Peyton Jones on the history of Haskell. It's a wonderful talk that details the rather interesting history of Haskell and how the language has evolved over time.</p>
<p>This was the first time I've watched a talk by SPJ and I was energized by how passionate he is about functional programming.</p>
<p>That's all I have for this week.</p>
<p>Until next time,</p>
<p>Marshall</p>
Cold Hands2020-01-13T00:00:00+00:002020-01-13T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0007-cold-hands/<p>Greetings, travelers.</p>
<p>This weekend I got to enjoy some unseasonably warm weather. On Saturday it was a balmy 65°F (that's 18°C for all you SI people), and I only needed a hoodie when I went outside! Sadly the temperatures have dropped again, so I'm back to being cold all the time.</p>
<p>One thing that I become increasingly aware of during the winter months is just how bad my circulation is. I'll be sitting at my desk, either at home or at work, and suddenly notice that my hands have turned a nice shade of purple and feel like slender icicles. There have been occasions where my hands start to stiffen up from the cold and start to impede my typing abilities. Maybe I should start coding with gloves on?</p>
<h2 id="cool-findings">Cool Findings</h2>
<p>Here's some cool stuff I found this week:</p>
<h3 id="crdts-for-mortals">CRDTs for Mortals</h3>
<p>James Long shared a video of his talk, "<a href="https://youtu.be/DEcwa68f-jY">CRDTs for Mortals</a>", from dotJS 2019.</p>
<p>This was the first time I had heard of <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">CRDTs</a> (conflict-free replicated data types), and the James' talk served as a good introduction for why they're useful.</p>
<p>After watching his talk I discovered <a href="https://github.com/rust-crdt/rust-crdt">rust-crdt</a>, which is a Rust crate providing a number of CRDT implementations.</p>
<h3 id="vvvvvv-open-sourced">VVVVVV Open-sourced</h3>
<p>This week the <a href="https://en.wikipedia.org/wiki/VVVVVV">VVVVVV</a> codebase was <a href="https://github.com/TerryCavanagh/VVVVVV">open-sourced</a>. I haven't played the game, but I have heard of it, so it was cool to poke around in the source code for a bit.</p>
<p>It also looks like there have been some contributions to the repository since being open-sourced.</p>
<p>Alright, my hands are getting cold.</p>
<p>Until next time,</p>
<p>Marshall</p>
A Return to Normalcy2020-01-05T00:00:00+00:002020-01-05T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0006-a-return-to-normalcy/<p>Happy New Year, travelers!</p>
<p>I hope that 2020 has been kind to you so far and that you are all settling back into your normal schedules. This week is my first full week back at work, and I'm looking forward to getting back into the normal swing of things.</p>
<p>Over the past few days I've been working on a Telegram bot to help me with my Telegram-based journal. Affectionately named "Daybook Tender", my bot currently has support for clocking in and out of work. The commands are sent and replied to in my daybook so that I can keep a record of my working hours over time. Also, when I clock out for the day my bot replies with the amount of hours I've worked so far during the week.</p>
<p>There are more features I want to add in the short term, as well as adding better persistence and a more permanent hosting solution.</p>
<p>It feels good to have already made some progress towards a couple of my goals this early into the year. This strong early push has made me more motivated to keep working at these goals, as well as my other goals.</p>
<p>In other news, today I started watching <em>The Witcher</em> on Netflix. So far I like the show a lot, and think it's really well-done. Although the whole time I'm watching it I can't help but want to go back and play The Witcher 3: Wild Hunt some more.</p>
<p>Until next time,</p>
<p>Marshall</p>
2019 in Review2019-12-31T04:24:24.530+00:002019-12-31T04:24:24.530+00:00Unknownhttps://maxdeviant.com/posts/2019/in-review/<p>It's been a while since I've written a proper year in review. The last one I wrote was in <a href="../../2016/in-hindsight">2016</a>. This gap bums me out a bit, as I really like going back through my yearly reviews and reflecting on them.</p>
<p>I can't go back and change the past, but I can do a better job moving forward, so let's get into it!</p>
<h2 id="accomplishments">Accomplishments</h2>
<ul>
<li>
<p>Got married! In January I married the most amazing woman, and I couldn't be happier. Marriage has been quite the adventure so far, but I wouldn't want it any other way. It's hard to express just how thankful I am for Heather. It's so reassuring to have someone who I know will stick with me through thick and thin and will always have my back, just like I have hers.</p>
</li>
<li>
<p>Visited Japan. This was something that has been on my bucket list for a while, and earlier this month Heather and I spent a little over a week in Tokyo. Japan is a magical place and I can't wait until I can return there (hopefully with some significantly-improved Japanese language skills).</p>
</li>
<li>
<p>Finally got around to installing <a href="https://nixos.org/">NixOS</a>. NixOS has been on my radar for a long time, but I never had the courage to pull the trigger on it. Now that I have, I can confidently say that it is the best Linux environment I have ever had. Full stop.</p>
</li>
<li>
<p>Received the Gravic Core Award. This award is the highest honor that my employer bestows upon one employee each year who embodies the company's core values. Ever since I started working at Gravic I have wanted to receive it, and this year my wish was granted. This came as a complete surprise to me and when my name was announced I was absolutely stunned.</p>
</li>
<li>
<p>Launched my own <a href="https://maxdeviant.com/posts/2019/in-review/@newsletters/errata-exist/_index.md">newsletter</a>. I write about software development, self improvement, and lots of other assorted things. I'm using <a href="https://buttondown.email/">Buttondown</a> to run it, and it's been a pleasure to use so far.</p>
</li>
<li>
<p>Wrote a handful of blog posts. I've been consistently terrible at writing blog posts year after year, and this year I've written a whopping <strong>nine</strong> (including the one you are reading right now). This is a marked improvement, one that I hope to continue in the coming year.</p>
</li>
</ul>
<h2 id="failures">Failures</h2>
<ul>
<li>Stressed too much over work</li>
<li>Not great work/life balance</li>
<li>Didn't keep up with friends enough</li>
<li>A bunch of books are still sitting unread on my shelf</li>
</ul>
<h2 id="stats">Stats</h2>
<h3 id="code">Code</h3>
<p>I authored 5,470 commits at work and another 1,183 commits on GitHub, for a grand total of 6,653 commits.</p>
<p>At work I wrote a lot of F# code, which has been great. Despite a few minor shortcomings, F# is a really great language. It's been fun learning all the nuances of it, especially in regards to interop with other .NET languages.</p>
<p>In my spare time I wrote some Rust, some Haskell, and a bit of Elm. These three languages are all a joy to use and I want to spend more timing building stuff with them.</p>
<h3 id="music">Music</h3>
<p>I listen to a lot of music every year, and this year was no different.</p>
<p>These are my top albums of 2019, by play count:</p>
<ol>
<li><a href="https://open.spotify.com/album/1GYVNOzwhx1nMcIJDogSNp?si=6bNAReCMSHaydKmtqKF-Dg">Death Race For Love</a> - Juice WRLD</li>
<li><a href="https://open.spotify.com/album/6I9VeBPPyAJuGpsW7n687S?si=xO36r__KSXeOjPfDdxE8kQ">Shadows</a> - The New Division</li>
<li><a href="https://open.spotify.com/album/06vukn88R2dOCd6sfMaogd?si=TLpsjuDIR0GGcuPiXyCZiw">Ghetto Lenny's Love Songs</a> - SAINt JHN</li>
<li><a href="https://open.spotify.com/album/13gyWqtgThuxh1cq68WjUo?si=iZX0maxgSCSBPSlXDtLXAA">Shadow_Movement (Deluxe Edition)</a> - The Anix</li>
<li><a href="https://open.spotify.com/album/7qoAggkU1ZQraacxXRDJXN?si=mKP1I7g9RBeXqYNl_IevAg">Even Though You're Gone</a> - Echos</li>
<li><a href="https://open.spotify.com/album/6OAnceN5hxlsDV9nmv4mm1?si=7S047rwcR2KPlr8VsIVRtA">Ivory</a> - BITWVLF</li>
<li><a href="https://open.spotify.com/album/78LBmUOdg2KeGk0mSQ1lTs?si=XlHtdY-qQOOo_CrIYSYU7Q">Betty</a> - Betty Who</li>
<li><a href="https://open.spotify.com/album/4aJdRGvDt8BAU8Po8Sr3dg?si=luUvUkXgQfy7LhQBEA5rQQ">Ephemeral</a> - Mr.Kitty</li>
<li><a href="https://open.spotify.com/album/1qnAzDoa24ZYCZDzmP74sL?si=kYow4jRiTsaSMPiuCp77qA">ASYLUM</a> - A R I Z O N A</li>
<li><a href="https://open.spotify.com/album/5qFIdWJCk3OLktSMfZug06?si=Z7FrQfCsQWWvPgszMdgzow">River</a> - BITWVLF</li>
<li><a href="https://open.spotify.com/album/4g1ZRSobMefqF6nelkgibi?si=o8h6GEvzTh6ZX8ebfw1UYw">Hollywood's Bleeding</a> - Post Malone</li>
<li><a href="https://open.spotify.com/album/0LPBRqGk7q7MEs1dzj6nrt?si=0vBDCFRuRM69JoAh9Wp5gw">Figments</a> - Forhill</li>
<li><a href="https://open.spotify.com/album/0FgZKfoU2Br5sHOfvZKTI9?si=4IrFDHrJS5Km15LbDNqEPw">JESUS IS KING</a> - Kanye West</li>
<li><a href="https://open.spotify.com/album/7dqpveMVcWgbzqYrOdkFTD?si=TCBJoGkgQmGGNt50Pg9saw">Care Package</a> - Drake</li>
<li><a href="https://open.spotify.com/album/1wIuFndiINuEZLFg3OIWGf?si=rJby9EwBTTu6lmin7lVU4g">VOL. 4 :: SLAVES OF FEAR</a> - HEALTH</li>
<li><a href="https://open.spotify.com/album/1aMtc9GO9PE3bqFd0pu4C4?si=R0Ap_ieuQC2b-AYSJV-nAA">Wide-Eyed</a> - Said the Sky</li>
<li><a href="https://open.spotify.com/album/3ZHBMDCb4NrguIr1EaDwZK?si=iSjtbUgzSYqvMbAnvSTOPQ">Voices</a> - Kamandi</li>
<li><a href="https://open.spotify.com/album/09dfll1eY4PRh0nSWIuFBi?si=qnYcDvNdR8C3lZ1fPTMl3g">'Til the End</a> - MitiS</li>
<li><a href="https://open.spotify.com/album/1JaCRg0kiQCxm2jxIby648?si=iBG4KkPVRHG9wT5gjMrXAw">A Pill for Loneliness</a> - City and Colour</li>
<li><a href="https://open.spotify.com/album/1shfaj98aEPsCz6k6B2O9o?si=SyLiUyIzR6qQ-RPlmdeZbw">Redux</a> - Cushy</li>
</ol>
<h2 id="goals-for-2020">Goals for 2020</h2>
<ul>
<li>Write more Rust</li>
<li>Finish and publish a game</li>
<li>Keep a journal</li>
<li>More home-cooked meals</li>
<li>Take better care of my body</li>
<li>Write a newsletter a week</li>
<li>Continue writing more blog posts</li>
</ul>
<p>As we enter this next decade I'm excited for all of the possibilities and opportunities that it holds. I will do my best to make the most of the time I have been given. I look forward to seeing what unfolds in my life and in the lives of those around me.</p>
Some Goals for 20202019-12-31T00:00:00+00:002019-12-31T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0005-some-goals-for-2020/<p>Hello again, travelers.</p>
<p>I apologize for the lapse in communiques; the end of this year has been rather hectic for me. Ever since getting back from Japan things have been absolutely chaotic with the Christmas season. Buying gifts (something that I seem to put off until the last minute every year), celebrating with family, and going to work a few days somewhere in there all make for a rather tumultuous mix. Let's just say that I'll be happy to get back into a more consistent routine.</p>
<p>This time of the year always feels a tad surreal. The days I did find myself in the office it was like a ghost town. When I wasn't in the office it felt hard keeping track of the days. It is within this surreality that I find myself reflecting a lot on things.</p>
<p>I wrote my <a href="https://maxdeviant.com/posts/2019/2019-in-review/">2019 year-in-review</a>: an almost-annual tradition of mine that has been shunted to the wayside in the past couple of years. I love writing (and reading!) year-in-review posts, so I feel a little ashamed for letting two years transpire without a proper write-up.</p>
<p>I won't go into all of the details of my year-in-review post, but I will touch on the goals I have set for myself going into 2020.</p>
<h3 id="write-more-rust">Write more Rust</h3>
<p>For the uninitiated, <a href="https://www.rust-lang.org/">Rust</a> is "a language empowering everyone
to build reliable and efficient software." It's also one that I have come to love, despite not having used it as heavily as I would like.</p>
<p>Rust manages to feel like a high-level language while still giving you the fine-tuned performance of a lower-level language like C or C++. As someone who has never really done much low-level programming since my high schools days—in which I couldn't wrap my head around pointers and swore off C++ entirely—learning Rust has been a return to the fundamentals of programming and memory management.</p>
<p>Given that I want to continue getting into video game development, a domain where performance is key, Rust is the obvious choice for someone like me who is used to high-level languages and well-aware of the dangers of writing memory-safe C and C++.</p>
<h3 id="finish-and-publish-a-game">Finish and publish a game</h3>
<p>I want this to be the year that I finally release a video game. I know that it won't be some magnum opus, or even a title that holds up against the games that I hold dear, but it will be out in the world and it will be my own.</p>
<p>To that end, I really just need to focus on <a href="https://geometrian.com/programming/tutorials/write-games-not-engines/">making games, not engines</a>.</p>
<h3 id="keep-a-journal">Keep a journal</h3>
<p>This goal is one that continually finds its way onto my list, but never seems to get completed. I have tried a variety of approaches to journaling over the years, all of which have failed to help me journal for an appreciable amount of time.</p>
<p>The approach that I am trying this time around, which I blatantly stole off a commenter on Hacker News, is to use <a href="https://telegram.org/">Telegram</a> as my journal. I setup a private Telegram channel for myself and I simply post messages in this channel as my means of journaling.</p>
<p>I am already in the Telegram app daily to chat with friends, so there is next to no friction in using it for journaling.</p>
<h3 id="more-home-cooked-meals">More home-cooked meals</h3>
<p>I love to cook, and around our house I'm the one who cooks dinner every night. But there are some nights where I come home from work and I just don't feel like cooking. Sometimes this results in me whipping up a quick meal that probably isn't as healthful as it could be. Other times we'll just order food for delivery, which is neither good for our wallet or our bodies.</p>
<p>I want to become better about planning out and cooking healthy meals for my wife and me. This will take some work to think about what I want to make ahead of time so that she knows what to get from the store.</p>
<p>As part of this I also want to introduce more variety into my cooking, as I tend to rotate through a small subset of meals.</p>
<h3 id="take-better-care-of-my-body">Take better care of my body</h3>
<p>The past few years have not been good for me in terms of taking care of my body. I skip meals, eat junk food, and don't get nearly enough exercise. While I have been lucky to not have any major health issues arise so far from this behavior, I know it's not sustainable in the long-run.</p>
<p>I want to take better care of my body, which means eating healthier, exercising more, and getting adequate sleep.</p>
<p>This goal is loosely related to the previous one, in that I hope cooking more meals at home will help me to eat healthier.</p>
<h3 id="write-a-newsletter-a-week">Write a newsletter a week</h3>
<p>I started this newsletter pretty late into 2019 and I <strong>still</strong> managed to miss three issues of <em>Errata Exist</em>!</p>
<p>In 2020 I want to write one issue of <em>Errata Exist</em> per week. Even if I miss my goal of having it sent out by Monday morning, I want to make sure that an issue goes out every week, without fail.</p>
<p>I recognize that this will probably be a bit of a push for me, but I firmly believe that it is within my limits to accomplish.</p>
<h3 id="continue-writing-more-blog-posts">Continue writing more blog posts</h3>
<p>2019 was a good year for my blog post output. I wrote nine blog posts, which is a significant improvement over the one or two I would write per year in the past.</p>
<p>I want to maintain this upward trajectory and continue writing blog posts in 2020.</p>
<hr />
<p>As we head into 2020, and the next decade, I am looking forward to all of the things that are in store. If you have year-in-review posts for 2019 or goals/resolutions for 2020, please, share them with me! I would love to hear them.</p>
<p>Happy New Year!</p>
<p>Marshall</p>
Advent, Expressivity, and Debuggers2019-12-02T00:00:00+00:002019-12-02T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0004-advent-expressivity-and-debuggers/<p>Hello again, fellow interstellar travelers.</p>
<p>I am writing this to you from 34,000 feet in the air as I fly towards Tokyo. I'll try to include some pictures from my trip in the next issue.</p>
<p>It's December, which means that <a href="https://adventofcode.com/">Advent of Code</a> is underway! For the uninitiated, Advent of Code is an annual event during the advent season—hence the name—where you solve puzzles that are released daily. You can use any language you want to solve them (one Advent of Code-er uses <a href="https://github.com/thatlegoguy/AoC2019">Excel</a> to solve the puzzles) and a new puzzle is released daily up through Christmas.</p>
<p>This year I'll be using Haskell as my language of choice for solving the puzzles. My goal is to stick with it for the entire duration, as I usually tend to drop off around day four or five. If you're interested in following my progress, you can check out my <a href="https://github.com/maxdeviant/advent-of-code">Advent of Code repo</a>. Just don't cheat yourself by looking at my solutions before you solve them for yourself!</p>
<p>Personally, I think it's a great way to get your feet wet in a new language. As someone who has a cursory knowledge of Haskell, I've already learned a bunch in the two days' worth of puzzles that I've solved. Things that one might take for granted in another language, like splitting a string on a certain delimiter or parsing a string into an integer, are all things that Advent of Code can help you learn how to do in your language of choice.</p>
<p>One of the things that I've noticed while solving the puzzles is just how expressive Haskell is. For the most part my solutions to the problems read almost exactly like the problem definitions.</p>
<p>Back when I was in college and writing mostly Java code, I remember feeling so powerful when using an IDE like IntelliJ that would take care of so much of the program boilerplate for me. All of the auto-completions and auto-refactorings that were available made it so quick to write and change code, especially when compared to some of the educational IDEs that other students were using.</p>
<p>When I'm using Haskell—or any other language with similar expressivity—I feel that same sense of power. Although now the power comes solely from the language; no additional tools needed. In fact, right now I don't even have an IDE-like environment setup for Haskell. I have syntax highlighting installed with VS Code, but other than that I just rely on the Haskell compiler and REPL when writing Haskell code.</p>
<p>That being said, I do think that language servers and editor integration are <em>great</em> features to have, but I think it is important to make sure they don't become a crutch. I'm sure that there are many developers who are used to using their IDE for everything and would flounder about if they had to write code in a regular text editor and compile their code directly via the compiler.</p>
<p>This same line of thinking extends to debuggers as well. I know developers who would be hopelessly lost without their debuggers and breakpoints. There was a point where I was a heavy user of the debugger, but I try to stay away from it these days.</p>
<p>When faced with a bug, the first thing I like to do is read through the code and attempt to figure out why the bug is happening without running any code at all. In this case, my brain is the only debugger. I would say that in the vast majority of cases this alone is sufficient to diagnose an issue. I can look at the code, make some educated guesses as to where the problem lies, and then I'll usually try to capture those guesses in a test, or just go ahead and apply the appropriate fix. It is only once I have some idea of where the problem lies that I will start actually executing the code and breaking out the debugger, if needed.</p>
<p>I think that being able to reason about programs and debug them in one's head is a very important skill to have. It forces you to consider the program and the bug holistically, rather than applying a fix to what very well may be just a symptom of a larger problem. Using a debugger has a way of pushing you towards zeroing in on just one location in the code and can cause you to lose the broader context.</p>
<p>Another thing that I've come to resent about debuggers is just how bad they are for debugging issues in distributed, asynchronous programs. Asynchrony alone is enough for me to want to stay away from a debugger, as things like breakpoints firing in an unexpected fashion can lead to a very confusing debugging session. I find that "<code>printf</code> debugging" tends to be much more effective for these types of programs.</p>
<p>Until next time,</p>
<p>Marshall</p>
Rust 20202019-11-28T00:42:10.509+00:002019-11-28T00:42:10.509+00:00Unknownhttps://maxdeviant.com/posts/2019/rust-2020/<p>When Rust put out a <a href="https://blog.rust-lang.org/2019/10/29/A-call-for-blogs-2020.html">call for blogs for 2020</a> I knew that I wanted to answer. I love pretty much everything about Rust, and I want to be able to use Rust as my tool of choice when building anything. However, right now there is some marked pain when working on certain kinds of projects in Rust.</p>
<p>With that said, I think the theme of a Rust 2021 edition, and what Rust should focus on in 2020, should be <strong>Capability</strong>.</p>
<p>There are <a href="https://www.arewewebyet.org/">lots</a> <a href="https://areweguiyet.com/">of</a> <a href="http://arewegameyet.com/">sites</a> that seek to answer the question "Are we <em>X</em> yet?" for Rust. And unfortunately, the answer to that question for most domains is "not quite". While it is possible to build web servers, GUIs, and games in Rust, the ecosystem just isn't quite there yet for the masses.</p>
<p>As someone who has attempted projects in a variety of spaces, so far the only one that has felt <em>really</em> nice is building CLIs in Rust. Crates like <a href="https://crates.io/crates/clap"><code>clap</code></a> and <a href="https://crates.io/crates/structopt"><code>structopt</code></a> are just incredible to use and really make the choice of Rust for building a CLI app a no-brainer.</p>
<p>I want that same feeling I get from building a CLI app to extend to building a web service, a GUI app, a game, or pretty much anything else.</p>
<p>I think that Rust should focus on providing the foundation for users to build any program under the sun in Rust and have a world-class experience while doing so.</p>
<p>What this might look like:</p>
<ul>
<li>Stabilizing nightly features for frameworks like <a href="https://rocket.rs/">Rocket</a> to go stable</li>
<li>Coordinating with working groups to identify what additions or improvements to the Rust compiler would help them advance their goals</li>
</ul>
<p>Personally, I would love to see higher-kinded types (HKT)—perhaps by adding <a href="https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md">associated-type constructors</a>—added to Rust, as this would give crate authors a very powerful tool for designing abstractions.</p>
<p>I just wanted to end by saying a big thank you to all the members of the Rust Core Team for all of the hard work that you do and the care with which you shepherd the Rust language and its community.</p>
Life's Too Short to Not Have Friends2019-11-25T00:00:00+00:002019-11-25T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0003-lifes-too-short-to-not-have-friends/<p>Greetings, friends.</p>
<p>I find that I am very generous with my usage of the term "friend", perhaps more than most people. A lot of my time is spent in online communities, and many of the people who I consider my friends I have never met in person, or even heard their voice. The majority of these people I might never meet in person.</p>
<p>Part of this viewpoint undoubtedly stems from my growing up overseas. I grew up in an environment where people were always coming and going. This sort of environment doesn't permit long-winded lead times in friendships: the period that most people would refer to someone else as an "acquaintance" as opposed to a "friend". Life's too short to not have friends.</p>
<p>Two years ago I took a trip to San Francisco to visit one of my high school friends from China. While there I had the opportunity to meet up with some of my other online friends. There's something so strange about meeting up with someone who you've only ever communicated digitally with. Yet at the same time, it felt just as familiar as meeting up with my high school friend.</p>
<p>On Thursday I learned that one of my online friends, Jerold Haas, died last year. I took some time to <a href="https://maxdeviant.com/posts/2019/jerold-haas/">reflect on our friendship</a>.</p>
<p>Life's too short to not have friends.</p>
<p>Your friend,</p>
<p>Marshall</p>
Jerold Haas2019-11-21T14:53:23.644+00:002019-11-21T14:53:23.644+00:00Unknownhttps://maxdeviant.com/posts/2019/jerold-haas/<p>Today I learned that Jerold Haas, aka CompositionFore, is dead.</p>
<p>A friend shared with me <a href="https://www.wired.com/story/strange-life-mysterious-death-of-virtuoso-coder/">a Wired article</a> about his life, disappearance, and death. The article is interesting and well-written in its own right, telling a tale that some have likened to <a href="https://en.wikipedia.org/wiki/Into_the_Wild_(book)"><em>Into the Wild</em></a>.</p>
<p>In light of the news, I thought I would take some time to reflect on our relationship.</p>
<hr />
<p>Jerold and I were friends. By most people's standards we would probably be considered mere internet acquaintances, but debating the semantics of friendship can be left for another time.</p>
<p>I first met Jerold in the Merveilles Slack. If memory serves, we initially connected over a shared love of F#. He was a seasoned F# programmer and I was an F# beginner. Much of our interaction was in regards to working on <a href="https://github.com/FsCMS/FsCMS">FsCMS</a>, an F#-based CMS focused on modularity.</p>
<p>I spent some time in Jerold's private Discord during the short period I was involved with FsCMS and for a little while after. We mostly just chatted via messages, but on a few occasions we did get on voice to talk. One of the things that I remember from the times we spoke was just how raucous his laugh was.</p>
<p>I didn't end up contributing much to FsCMS before my involvement petered out. As my involvement with the project ended, so did my communication with Jerold.</p>
<p>Unfortunately our Slack history has been lost to Slack's free-tier history limits and I am no longer a member of his Discord server (probably as a result of one of my server purges). It's a shame; I would have liked to go back through our chat logs.</p>
<p>The last remaining digital memories of Jerold lie tucked away in various corners of the internet:</p>
<ul>
<li><a href="https://twitter.com/jeroldhaas">Twitter (@jeroldhaas)</a></li>
<li><a href="https://twitter.com/compositionfore">Twitter (@CompositionFore)</a></li>
<li><a href="https://github.com/jeroldhaas">GitHub (@jeroldhaas)</a></li>
<li><a href="https://github.com/composition4">GitHub (@composition4)</a></li>
<li><a href="https://www.linkedin.com/in/jeroldhaas/">LinkedIn</a></li>
<li><a href="https://soundcloud.com/compositionfore">SoundCloud</a></li>
<li><a href="https://www.youtube.com/channel/UCNcc6UZWwPEVQLwo86Z_Fbw">YouTube</a></li>
<li><a href="https://compositionfore.itch.io/">itch.io</a></li>
</ul>
<p>Rest in peace, Jerold. You will be missed.</p>
SAD Times for Low Lives2019-11-20T00:00:00+00:002019-11-20T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0002-sad-times-for-low-lives/<p>Hello there, fellow sojourner.</p>
<p>This issue took longer for me to get around to writing than I care to admit. I had every intention of sending it out Sunday night, but when I sat down to write it I couldn't bring myself to put words on the screen.</p>
<p>I suppose that this was part of my motivation for starting a newsletter in the first place. By having a consistent place to write, my hope is that this resistance to writing will become easier to overcome.</p>
<h2 id="sad">SAD</h2>
<p>As we inch ever-closer to winter I have been starting to feel the effects of <a href="https://www.mayoclinic.org/diseases-conditions/seasonal-affective-disorder/symptoms-causes/syc-20364651">SAD</a>. I don't exactly remember when I became aware that I suffered from SAD, but I can't remember a time where I didn't feel down during the winter months.</p>
<p>This year I decided to take some steps to actively combat it, namely in the form of <a href="https://www.mayoclinic.org/tests-procedures/light-therapy/about/pac-20384604">light therapy</a>. I bought myself a <a href="https://www.amazon.com/gp/product/B0094HBU6I/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1">Verilux HappyLight</a> and spend thirty to forty-five minutes in front of it each morning. So far it has seemed to balance out my moods a bit, which is great.</p>
<p>A nice side-effect of starting light therapy has been the addition of some "me time" first thing in the morning. It's nice to have something to look forward to when first waking up rather than leaping straight into the day.</p>
<h2 id="new-blog-post">New Blog Post</h2>
<p>Last week I wrote about how <a href="https://maxdeviant.com/posts/2019/everything-is-a-value/">everything is a value</a> and what starts to happen when you treat more complex concepts as plain values.</p>
<p>I had a lot of fun writing this post, both because I enjoy the premise of it and because it was a chance to write some more Haskell.</p>
<p>Give it a read and let me know what you think!</p>
<h2 id="cool-findings">Cool Findings</h2>
<p>Here's some of the stuff I found while being on the internet this week.</p>
<h3 id="nomnoml">nomnoml</h3>
<p>If you're anything like me and find yourself in need of a way to draw flowcharts or diagrams every so often, look no further than <a href="http://www.nomnoml.com/">nomnoml</a>.</p>
<p>It supports lots of various graph types, like UML and database diagrams, and I really dig the aesthetic of it.</p>
<h3 id="what-s-new-in-es-2020">What's New in ES 2020</h3>
<p>I had a good chuckle reading "<a href="https://jaredpalmer.com/blog/whats-new-in-es-2020">What's New in ES 2020</a>" by Jared Palmer.</p>
<p>Until our paths cross again,</p>
<p>Marshall</p>
Everything is a Value2019-11-13T00:44:33.732+00:002019-11-13T00:44:33.732+00:00Unknownhttps://maxdeviant.com/posts/2019/everything-is-a-value/<p>What is a value? We're going to avoid the <a href="https://en.wikipedia.org/wiki/Values_(Western_philosophy)">philosophical</a> kind of values for now and focus on the computer science kind.</p>
<p>According to Wikipedia:</p>
<blockquote>
<p>In computer science, a value is the representation of some entity that can be manipulated by a program.</p>
</blockquote>
<p>Normally when we think of values we think of <em>primitive</em> values, like numbers or strings. We can perform operations on these values, such as adding two numbers together or concatenating two strings together to produce a new value:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">> </span><span style="color:#f78c6c;">1 </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">1
</span><span style="color:#f78c6c;">2
</span><span>
</span><span>ghci</span><span style="color:#89ddff;">> </span><span style="color:#f78c6c;">2 </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">2
</span><span style="color:#f78c6c;">4
</span><span>
</span><span>ghci</span><span style="color:#89ddff;">> "</span><span style="color:#c3e88d;">Hello</span><span style="color:#89ddff;">" ++ "</span><span style="color:#c3e88d;">, world!</span><span style="color:#89ddff;">"
</span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Hello, world!</span><span style="color:#89ddff;">"
</span></code></pre>
<p>Numbers, in particular, have some interesting properties afforded to them by the laws of mathematics, such as the associative property and the commutative property:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> (</span><span style="color:#f78c6c;">1 </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">1</span><span>) </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">2 </span><span style="color:#89ddff;">== </span><span style="color:#f78c6c;">1 </span><span style="color:#89ddff;">+</span><span> (</span><span style="color:#f78c6c;">1 </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">2</span><span>)
</span><span>True
</span><span>
</span><span>ghci</span><span style="color:#89ddff;">> </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">5 </span><span style="color:#89ddff;">== </span><span style="color:#f78c6c;">5 </span><span style="color:#89ddff;">+ </span><span style="color:#f78c6c;">3
</span><span>True
</span></code></pre>
<p>In this post we're going to explore treating complex types the same way we treat values, and what happens when we do so.</p>
<h2 id="mmo-tivating-example">MMO-tivating Example</h2>
<p>Let's pretend we're creating a fantasy <a href="https://en.wikipedia.org/wiki/Massively_multiplayer_online_role-playing_game">MMORPG</a> and we want to track some basic statistics about each player. We'll define a record to hold the stats that we're interested in tracking:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">data </span><span>PlayerStats </span><span style="color:#89ddff;">=
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">:: </span><span>Float
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">:: </span><span>Int
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">:: </span><span>Int
</span><span> }
</span><span> </span><span style="color:#c792ea;">deriving</span><span> (</span><span style="color:#c3e88d;">Eq</span><span>, </span><span style="color:#c3e88d;">Show</span><span>)
</span></code></pre>
<p>At some point we'll end up with a list of <code>PlayerStats</code>, probably retrieved from a database or some other means of persistence. That might look something like this:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">allPlayerStats </span><span style="color:#c792ea;">::</span><span> [</span><span style="font-style:italic;color:#c792ea;">PlayerStats</span><span>]
</span><span>allPlayerStats </span><span style="color:#89ddff;">=
</span><span> [ PlayerStats {hoursPlayed </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">2</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">5</span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">25</span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">39</span><span>}
</span><span> </span><span style="color:#89ddff;">, </span><span>PlayerStats {hoursPlayed </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">14</span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">167</span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">543</span><span>}
</span><span> </span><span style="color:#89ddff;">, </span><span>PlayerStats {hoursPlayed </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">7</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">75</span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">125</span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">234</span><span>}
</span><span> </span><span style="font-style:italic;color:#4a4a4a;">-- ...
</span><span> ]
</span></code></pre>
<p>Now suppose we want to aggregate these statistics to get the totals across all players. One approach to this might be to write a function to sum each stat individually:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">totalHoursPlayed </span><span style="color:#c792ea;">::</span><span> [</span><span style="font-style:italic;color:#c792ea;">PlayerStats</span><span>] </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Float
</span><span>totalHoursPlayed </span><span style="color:#89ddff;">=</span><span> foldr </span><span style="color:#82aaff;">(+) </span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">.</span><span> map hoursPlayed
</span><span>
</span><span style="color:#82aaff;">totalMonstersKilled </span><span style="color:#c792ea;">::</span><span> [</span><span style="font-style:italic;color:#c792ea;">PlayerStats</span><span>] </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Int
</span><span>totalMonstersKilled </span><span style="color:#89ddff;">=</span><span> foldr </span><span style="color:#82aaff;">(+) </span><span style="color:#f78c6c;">0 </span><span style="color:#89ddff;">.</span><span> map monstersKilled
</span></code></pre>
<p>While this approach works, it isn't exactly ideal. For one, we need to define a new function each time we have a new stat that we want to aggregate. Notice how we don't have a <code>totalGoldEarned</code> function defined. If we wanted to get the total amount of gold earned across all players we would need to write a <code>totalGoldEarned</code> function.</p>
<p>Another shortcoming of this approach is that each time we calculate one of these stats we need to traverse the entire list of player stats.</p>
<p>We can do better!</p>
<p>Let's write a function that will sum all of our player stats:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">sumPlayerStats </span><span style="color:#c792ea;">::</span><span> [</span><span style="font-style:italic;color:#c792ea;">PlayerStats</span><span>] </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">PlayerStats
</span><span>sumPlayerStats </span><span style="color:#89ddff;">=
</span><span> foldr
</span><span> (</span><span style="color:#89ddff;">\</span><span>a b </span><span style="color:#89ddff;">->
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">=</span><span> hoursPlayed a </span><span style="color:#89ddff;">+</span><span> hoursPlayed b
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">=</span><span> monstersKilled a </span><span style="color:#89ddff;">+</span><span> monstersKilled b
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">=</span><span> goldEarned a </span><span style="color:#89ddff;">+</span><span> goldEarned b
</span><span> })
</span><span> PlayerStats {hoursPlayed </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">0</span><span>}
</span></code></pre>
<p>This looks much better. Now we can use <code>sumPlayerStats</code> to run through the list of stats <em>once</em> and then pull out whichever of the accumulative stats that we want:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> sumPlayerStats </span><span style="color:#89ddff;">$</span><span> allPlayerStats
</span><span>PlayerStats {hoursPlayed </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">24</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">25</span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">317</span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">816</span><span>}
</span><span>
</span><span>ghci</span><span style="color:#89ddff;">></span><span> hoursPlayed </span><span style="color:#89ddff;">.</span><span> sumPlayerStats </span><span style="color:#89ddff;">$</span><span> allPlayerStats
</span><span style="color:#f78c6c;">24</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">25
</span></code></pre>
<p>While this approach is perfectly fine, it turns out we can still do better.</p>
<p>Let's take a step back and return to the concept of values.</p>
<p>Say we want to sum a list of numbers. For example, here's how we could sum the numbers from one to ten:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> foldr </span><span style="color:#82aaff;">(+) </span><span style="color:#f78c6c;">0 </span><span style="color:#89ddff;">$</span><span> [</span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">..</span><span style="color:#f78c6c;">10</span><span>]
</span><span style="color:#f78c6c;">55
</span></code></pre>
<p>To sum a list of numbers we're still using <code>(+)</code>, just like when we performed <code>1 + 1</code> or <code>2 + 2</code>. Can we make summing our <code>PlayerStats</code> just as simple as <code>1 + 1</code>?</p>
<p>Turns out, we can!</p>
<p>The first thing we'll need to do is define an instance of <code>Num</code> for <code>PlayerStats</code>:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">instance </span><span style="font-style:italic;color:#c792ea;">Num PlayerStats </span><span style="color:#c792ea;">where
</span><span> a </span><span style="color:#89ddff;">+</span><span> b </span><span style="color:#89ddff;">=
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">=</span><span> hoursPlayed a </span><span style="color:#89ddff;">+</span><span> hoursPlayed b
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">=</span><span> monstersKilled a </span><span style="color:#89ddff;">+</span><span> monstersKilled b
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">=</span><span> goldEarned a </span><span style="color:#89ddff;">+</span><span> goldEarned b
</span><span> }
</span><span> a </span><span style="color:#89ddff;">-</span><span> b </span><span style="color:#89ddff;">=
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">=</span><span> hoursPlayed a </span><span style="color:#89ddff;">-</span><span> hoursPlayed b
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">=</span><span> monstersKilled a </span><span style="color:#89ddff;">-</span><span> monstersKilled b
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">=</span><span> goldEarned a </span><span style="color:#89ddff;">-</span><span> goldEarned b
</span><span> }
</span><span> a * b </span><span style="color:#89ddff;">=
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">=</span><span> hoursPlayed a * hoursPlayed b
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">=</span><span> monstersKilled a * monstersKilled b
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">=</span><span> goldEarned a * goldEarned b
</span><span> }
</span><span> abs stats </span><span style="color:#89ddff;">=
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">=</span><span> abs </span><span style="color:#89ddff;">$</span><span> hoursPlayed stats
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">=</span><span> abs </span><span style="color:#89ddff;">$</span><span> monstersKilled stats
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">=</span><span> abs </span><span style="color:#89ddff;">$</span><span> goldEarned stats
</span><span> }
</span><span> signum stats </span><span style="color:#89ddff;">=
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">=</span><span> signum </span><span style="color:#89ddff;">$</span><span> hoursPlayed stats
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">=</span><span> signum </span><span style="color:#89ddff;">$</span><span> monstersKilled stats
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">=</span><span> signum </span><span style="color:#89ddff;">$</span><span> goldEarned stats
</span><span> }
</span><span> fromInteger i </span><span style="color:#89ddff;">=
</span><span> PlayerStats
</span><span> { hoursPlayed </span><span style="color:#89ddff;">=</span><span> fromInteger i
</span><span> </span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">=</span><span> fromInteger i
</span><span> </span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">=</span><span> fromInteger i
</span><span> }
</span></code></pre>
<p>While this might seem like a lot of code at first, I promise it is all worth it in the end. By defining <code>Num</code> for our <code>PlayerStats</code> record we've given it the ability to behave just like a number.</p>
<p>With that in place, look at what our <code>sumPlayerStats</code> from before turns into:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> foldr </span><span style="color:#82aaff;">(+) </span><span style="color:#f78c6c;">0 </span><span style="color:#89ddff;">$</span><span> allPlayerStats
</span><span>PlayerStats {hoursPlayed </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">24</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">25</span><span style="color:#89ddff;">,</span><span> monstersKilled </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">317</span><span style="color:#89ddff;">,</span><span> goldEarned </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">816</span><span>}
</span></code></pre>
<p>It's identical to when we summed the numbers from one to ten! By treating <code>PlayerStats</code> like a number we get all the benefits of a number, like being able to combine two instances using <code>(+)</code>.</p>
<p>One thing to note is that this does not always work. The reason we can do this with <code>PlayerStats</code> is because it is comprised solely of numeric fields.</p>
<p>So now we know how to treat records as values. Are there other things that we can treat as values?</p>
<h2 id="fun-with-functions">Fun with Functions</h2>
<p>Let's define an <code>isDivisibleBy</code> function that checks whether some integer is divisible by another integer:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">isDivisibleBy </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Int </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Int </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>isDivisibleBy divisor n </span><span style="color:#89ddff;">=</span><span> n </span><span style="color:#89ddff;">`mod`</span><span> divisor </span><span style="color:#89ddff;">== </span><span style="color:#f78c6c;">0
</span></code></pre>
<p>We can then implement <code>isEven</code> in terms of <code>isDivisibleBy</code>, like so:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">isEven </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Int </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>isEven </span><span style="color:#89ddff;">=</span><span> isDivisibleBy </span><span style="color:#f78c6c;">2
</span></code></pre>
<p>With these two functions we can implement an <code>isDivisibleBy6</code> function using the <a href="https://en.wikipedia.org/wiki/Divisibility_rule">divisibility rules</a>:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">isDivisibleBy6 </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Int </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>isDivisibleBy6 n </span><span style="color:#89ddff;">=</span><span> isEven n && isDivisibleBy </span><span style="color:#f78c6c;">3</span><span> n
</span></code></pre>
<blockquote>
<p>We could also have implemented <code>isDivisibleBy6</code> just by doing <code>isDivisibleBy 6</code>, but we're doing it this way in order to identify a more general pattern.</p>
</blockquote>
<p>Let's examine <code>isDivisibleBy6</code> a little more closely. Notice how we have two terms: <code>isEven n</code> and <code>isDivisibleBy 3 n</code>. When the function is executed we'll evaluate one or both terms (<code>(&&)</code> in Haskell <a href="https://wiki.haskell.org/Short-circuiting">short-circuits</a> if the first term is <code>False</code>) and then use <code>(&&)</code> to compute the result.</p>
<p>Notice how we have an <code>n</code> on both sides of the "equation". On one side we take <code>n</code> as an argument to the function, and on the other we apply <code>n</code> to both terms.</p>
<p>Time for a brief detour. If we were to put this into mathematical terms, it's like having:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#f78c6c;">4 </span><span style="color:#89ddff;">+</span><span> x </span><span style="color:#89ddff;">=</span><span> (</span><span style="color:#f78c6c;">1 </span><span style="color:#89ddff;">+</span><span> x) </span><span style="color:#89ddff;">+</span><span> (</span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">+</span><span> x)
</span></code></pre>
<p>We can remove all of the <code>x</code>s from the equation and the equation will still hold true.</p>
<p>Returning to our <code>isDivisibleBy6</code> function, can we perform the same simplification here?</p>
<p>Let's try just removing <code>n</code> entirely and <code>(&&)</code>ing our two functions together:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> isEven && isDivisibleBy </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">$ </span><span style="color:#f78c6c;">6
</span><span style="color:#89ddff;"><</span><span>interactive</span><span style="color:#89ddff;">>:</span><span style="color:#f78c6c;">12</span><span style="color:#89ddff;">:</span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">:</span><span> error</span><span style="color:#89ddff;">:
</span><span> • Couldn't match expected </span><span style="color:#c792ea;">type</span><span> ‘Bool’
</span><span> with actual </span><span style="color:#c792ea;">type</span><span> ‘Int </span><span style="color:#89ddff;">-> </span><span>Bool’
</span><span> • Probable cause</span><span style="color:#89ddff;">:</span><span> ‘isEven’ is applied to too few arguments
</span><span> In the first argument </span><span style="color:#c792ea;">of</span><span> ‘(&&)’</span><span style="color:#89ddff;">,</span><span> namely ‘isEven’
</span><span> In the expression</span><span style="color:#89ddff;">:</span><span> isEven && isDivisibleBy </span><span style="color:#f78c6c;">3
</span><span> In the expression</span><span style="color:#89ddff;">:</span><span> isEven && isDivisibleBy </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">$ </span><span style="color:#f78c6c;">6
</span><span>
</span><span style="color:#89ddff;"><</span><span>interactive</span><span style="color:#89ddff;">>:</span><span style="color:#f78c6c;">12</span><span style="color:#89ddff;">:</span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">:</span><span> error</span><span style="color:#89ddff;">:
</span><span> • Couldn't match expected </span><span style="color:#c792ea;">type</span><span> ‘Integer </span><span style="color:#89ddff;">-></span><span> t’
</span><span> with actual </span><span style="color:#c792ea;">type</span><span> ‘Bool’
</span><span> • The first argument </span><span style="color:#c792ea;">of </span><span style="color:#82aaff;">($)</span><span> takes one argument</span><span style="color:#89ddff;">,
</span><span> but its </span><span style="color:#c792ea;">type</span><span> ‘Bool’ has none
</span><span> In the expression</span><span style="color:#89ddff;">:</span><span> isEven && isDivisibleBy </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">$ </span><span style="color:#f78c6c;">6
</span><span> In an equation for ‘it’</span><span style="color:#89ddff;">:</span><span> it </span><span style="color:#89ddff;">=</span><span> isEven && isDivisibleBy </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">$ </span><span style="color:#f78c6c;">6
</span><span> • Relevant bindings include it </span><span style="color:#89ddff;">::</span><span> t (bound at </span><span style="color:#89ddff;"><</span><span>interactive</span><span style="color:#89ddff;">>:</span><span style="color:#f78c6c;">12</span><span style="color:#89ddff;">:</span><span style="color:#f78c6c;">1</span><span>)
</span><span>
</span><span style="color:#89ddff;"><</span><span>interactive</span><span style="color:#89ddff;">>:</span><span style="color:#f78c6c;">12</span><span style="color:#89ddff;">:</span><span style="color:#f78c6c;">11</span><span style="color:#89ddff;">:</span><span> error</span><span style="color:#89ddff;">:
</span><span> • Couldn't match expected </span><span style="color:#c792ea;">type</span><span> ‘Bool’
</span><span> with actual </span><span style="color:#c792ea;">type</span><span> ‘Int </span><span style="color:#89ddff;">-> </span><span>Bool’
</span><span> • Probable cause</span><span style="color:#89ddff;">:</span><span> ‘isDivisibleBy’ is applied to too few arguments
</span><span> In the second argument </span><span style="color:#c792ea;">of</span><span> ‘(&&)’</span><span style="color:#89ddff;">,</span><span> namely ‘isDivisibleBy </span><span style="color:#f78c6c;">3</span><span>’
</span><span> In the expression</span><span style="color:#89ddff;">:</span><span> isEven && isDivisibleBy </span><span style="color:#f78c6c;">3
</span><span> In the expression</span><span style="color:#89ddff;">:</span><span> isEven && isDivisibleBy </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">$ </span><span style="color:#f78c6c;">6
</span></code></pre>
<p>Well, that didn't work out so well. The compiler is complaining because <code>(&&)</code> only works on <code>Bool</code>s, and we're trying to use it with an <code>Int -> Bool</code>.</p>
<p>But what if we could make a version of <code>(&&)</code> that knew how to work with an <code>Int -> Bool</code>?</p>
<p>We can define our own logical operators that work on <a href="https://en.wikipedia.org/wiki/Unary_function">unary</a> functions that return <code>Bool</code>:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>(&&&) </span><span style="color:#89ddff;">::</span><span> (a </span><span style="color:#89ddff;">-> </span><span>Bool) </span><span style="color:#89ddff;">-></span><span> (a </span><span style="color:#89ddff;">-> </span><span>Bool) </span><span style="color:#89ddff;">-></span><span> (a </span><span style="color:#89ddff;">-> </span><span>Bool)
</span><span>(&&&) f g </span><span style="color:#89ddff;">= \</span><span>a </span><span style="color:#89ddff;">-></span><span> f a && g a
</span><span>
</span><span style="color:#89ddff;">infixr </span><span style="color:#f78c6c;">3</span><span> &&&
</span><span>
</span><span style="color:#82aaff;">(|||) </span><span style="color:#c792ea;">::</span><span> (a </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool</span><span>) </span><span style="color:#c792ea;">-></span><span> (a </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool</span><span>) </span><span style="color:#c792ea;">-></span><span> (a </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool</span><span>)
</span><span style="color:#82aaff;">(|||)</span><span> f g </span><span style="color:#89ddff;">= \</span><span>a </span><span style="color:#89ddff;">-></span><span> f a </span><span style="color:#89ddff;">||</span><span> g a
</span><span>
</span><span style="color:#89ddff;">infixr </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">|||
</span></code></pre>
<blockquote>
<p>I couldn't find a way to overload <code>(&&)</code> and <code>(||)</code> for functions, hence why we need to use slightly different operators for them.</p>
</blockquote>
<p>Now let's try out the expression we tried before, but this time using our own <code>(&&&)</code> operator in place of <code>(&&)</code>:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> isEven &&& isDivisibleBy </span><span style="color:#f78c6c;">3 </span><span style="color:#89ddff;">$ </span><span style="color:#f78c6c;">6
</span><span>True
</span></code></pre>
<p>It works!</p>
<p>And here's what it would look like as a function declaration:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">isDivisibleBy6' </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Int </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>isDivisibleBy6' </span><span style="color:#89ddff;">=</span><span> isEven &&& isDivisibleBy </span><span style="color:#f78c6c;">3
</span></code></pre>
<p>We've now simplified the "equation" by removing the redundant <code>n</code> from both sides. What enabled us to do this was treating our functions as values and being able to combine them using logical operators.</p>
<p>While this particular example might appear to be of limited use (after all, we can just do <code>isDivisibleBy 6</code> instead), it does open the door to more possibilities.</p>
<p>It's time to use our imaginations again! This time pretend that we're running a streaming service. Our domain might look something like this:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">data </span><span>Viewer </span><span style="color:#89ddff;">=
</span><span> Viewer
</span><span> { age </span><span style="color:#89ddff;">:: </span><span>Int
</span><span> </span><span style="color:#89ddff;">,</span><span> isSubscriber </span><span style="color:#89ddff;">:: </span><span>Bool
</span><span> </span><span style="color:#89ddff;">,</span><span> hasParentalApproval </span><span style="color:#89ddff;">:: </span><span>Bool
</span><span> }
</span><span>
</span><span style="color:#82aaff;">is18OrOlder </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Viewer </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>is18OrOlder viewer </span><span style="color:#89ddff;">=</span><span> age viewer </span><span style="color:#89ddff;">>= </span><span style="color:#f78c6c;">18
</span></code></pre>
<p>Let's say that we have the following business requirement:</p>
<blockquote>
<p>"In order to be eligible for a giveaway a viewer must be a subscriber and either 1) be 18 or older or 2) have parental approval."</p>
</blockquote>
<p>Using our <code>(&&&)</code> and <code>(|||)</code> operators we can define a function that expresses this business requirement just as clearly as it was stated above:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">isEligibleForGiveaway </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Viewer </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>isEligibleForGiveaway </span><span style="color:#89ddff;">=</span><span> isSubscriber &&& (is18OrOlder </span><span style="color:#89ddff;">|||</span><span> hasParentalApproval)
</span></code></pre>
<p>We can also test out our <code>isEligibleForGiveaway</code> function to ensure it's working as expected:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>ghci</span><span style="color:#89ddff;">></span><span> isEligibleForGiveaway </span><span style="color:#89ddff;">$ </span><span>Viewer {age </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">19</span><span style="color:#89ddff;">,</span><span> isSubscriber </span><span style="color:#89ddff;">= </span><span>True</span><span style="color:#89ddff;">,</span><span> hasParentalApproval </span><span style="color:#89ddff;">= </span><span>False}
</span><span>True
</span><span>
</span><span>ghci</span><span style="color:#89ddff;">></span><span> isEligibleForGiveaway </span><span style="color:#89ddff;">$ </span><span>Viewer {age </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">21</span><span style="color:#89ddff;">,</span><span> isSubscriber </span><span style="color:#89ddff;">= </span><span>False</span><span style="color:#89ddff;">,</span><span> hasParentalApproval </span><span style="color:#89ddff;">= </span><span>False}
</span><span>False
</span><span>
</span><span>ghci</span><span style="color:#89ddff;">></span><span> isEligibleForGiveaway </span><span style="color:#89ddff;">$ </span><span>Viewer {age </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">11</span><span style="color:#89ddff;">,</span><span> isSubscriber </span><span style="color:#89ddff;">= </span><span>True</span><span style="color:#89ddff;">,</span><span> hasParentalApproval </span><span style="color:#89ddff;">= </span><span>True}
</span><span>True
</span></code></pre>
<p>You can see how as the business requirements change and new requirements are added it would be trivial to update our <code>isEligibleForGiveaway</code> function to incorporate the new requirements while still remaining readable and easy to reason about.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>In this post we've explored how we can treat complex types—like records and functions—as values, and gain a number of benefits in doing so.</p>
<p>By treating our <code>PlayerStats</code> record like a number we were able to sum a list of stats just as easily as we could with a list of <code>Int</code>s.</p>
<p>And by treating our functions as values we discovered that we could combine functions together using logical operators in order to clearly express business requirements in code.</p>
<p>I hope you enjoyed reading this, and hopefully learned something new that you can use in your day-to-day programming.</p>
<hr />
<p>If you liked this post and want to get notified about new ones like it, be sure to subscribe to my <a href="https://maxdeviant.com/newsletters/errata-exist/">newsletter</a>, <em>Errata Exist</em>.</p>
<p>And if you have any questions, comments, or other feedback, please <a href="https://maxdeviant.com/open-invite/">get in touch</a> with me. I'd love to hear from you!</p>
Greetings, Traveler2019-11-10T00:00:00+00:002019-11-10T00:00:00+00:00Unknownhttps://maxdeviant.com/newsletters/errata-exist/0001-greetings-traveler/<p>Greetings, traveler.</p>
<p>Welcome to the first issue of <em>Errata Exist</em>!</p>
<h2 id="on-errata-exist">On <em>Errata Exist</em></h2>
<p>Given that this is the first issue of <em>Errata Exist</em>, I thought I'd touch a bit on my ideas and goals for the newsletter.</p>
<p>Firstly, let's talk about the name. The term "Errata Exist" can be found at the top of various RFCs and is used to indicate that errors exist in the RFC and the reader should refer to the existing errata for the corrections.</p>
<p>I remember when I first saw "Errata Exist" on the <a href="https://tools.ietf.org/html/rfc6749">OAuth 2.0 RFC</a> something about it really resonated with me. As a software developer I'm well-acquainted with errors; finding and fixing them is a decently-sized part of my job. But I am also acquainted with errors as a human being. Errata exist within myself, and I am always looking to track down these errors and correct them.</p>
<p>This newsletter is about improving the quality of ourselves just as much as it is about improving the quality software that we write.</p>
<p>Right now my goal is to send out the newsletter weekly on Sunday nights so that it will be waiting in your inbox on Monday morning.</p>
<p>What you might expect in a weekly newsletter:</p>
<ul>
<li>Links to blog posts I have written</li>
<li>Assorted musings that may not warrant a full post</li>
<li>Links to articles and blog posts that I find interesting</li>
</ul>
<p>One of the goals of this newsletter is to encourage an open dialog between myself and you, the reader. If you have any thoughts on what I have written or shared, I would be thrilled if you would talk to me about it. I always have an <a href="https://maxdeviant.com/open-invite">open invitation</a> extended to you.</p>
<h2 id="new-blog-post">New Blog Post</h2>
<p>This week I published "<a href="https://maxdeviant.com/posts/2019/implementing-a-case-conversion-library-in-fsharp-and-haskell/">Implementing a Case Conversion Library in F# and Haskell</a>". In this post I take a small F# library I wrote for performing case conversion and convert it to Haskell.</p>
<p>I would encourage you to give it a read and let me know what you think. If you're like me and are familiar with F# but not Haskell then you can see how Haskell isn't really all that different (at least in the context of this particular problem).</p>
<p>If you're not familiar with either language—or even functional programming in general—then you can still see how this problem is solved in a functional style.</p>
<h2 id="article-recommendations">Article Recommendations</h2>
<p>I read a great article this week by Alexis King titled "<a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">Parse, don't validate</a>". What Lexi touches on in her article mirrors a lot of the thoughts that I've had recently about modeling data in such a way that illegal states are impossible to represent.</p>
<p>Until next time,</p>
<p>Marshall</p>
Implementing a Case Conversion Library in F# and Haskell2019-11-09T03:50:51.335+00:002019-11-09T03:50:51.335+00:00Unknownhttps://maxdeviant.com/posts/2019/implementing-a-case-conversion-library-in-fsharp-and-haskell/<p>At work I recently implemented a small library in F# for performing case conversion. The library is inspired by <a href="https://github.com/withoutboats/heck">heck</a>, a case conversion library for Rust.</p>
<p>After completing the initial implementation I thought it would be a fun exercise to implement the library in Haskell.</p>
<h2 id="creating-a-module">Creating a Module</h2>
<p>The first thing we'll need to do is create a module for the library.</p>
<p>In F# this is just a few lines:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">module </span><span>Casing
</span><span>
</span><span style="color:#c792ea;">open </span><span>System
</span></code></pre>
<p>We have slightly more work to do in the Haskell version since we're going to explicitly state which functions we want to export. In F# we can use visibility keywords to govern which functions are exposed outside of the module.</p>
<p>Here's what the top of our Haskell module looks like:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span>{-# OPTIONS -Wall #-}
</span><span>
</span><span style="color:#c792ea;">module </span><span>Casing
</span><span> ( </span><span style="color:#82aaff;">toCamelCase
</span><span> </span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">toPascalCase
</span><span> </span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">toSnakeCase
</span><span> </span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">toScreamingSnakeCase
</span><span> </span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">toKebabCase
</span><span> </span><span style="color:#89ddff;">, </span><span style="color:#82aaff;">toTitleCase
</span><span> ) </span><span style="color:#c792ea;">where
</span><span>
</span><span style="color:#c792ea;">import qualified </span><span>Data.Char </span><span style="color:#c792ea;">as </span><span>Char
</span><span style="color:#c792ea;">import </span><span>Data.List (</span><span style="color:#82aaff;">intercalate</span><span>)
</span><span style="color:#c792ea;">import </span><span>Data.Maybe (</span><span style="color:#82aaff;">catMaybes</span><span>)
</span></code></pre>
<h2 id="getting-some-help">Getting Some Help</h2>
<p>Now that we have our module setup, it's time to declare some helper functions.</p>
<p>Not all of these helper functions are strictly needed for the F# version, but I think that they make things a little more ergonomic.</p>
<blockquote>
<p>Side note, if you're wondering about what <code>(^)</code> is, it's an operator that performs function application, much like <code>($)</code> in Haskell.</p>
</blockquote>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">let (</span><span style="color:#f78c6c;">^</span><span style="color:#c792ea;">) = (<|)
</span><span>
</span><span style="color:#c792ea;">module private </span><span>List </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">let </span><span>mapHead</span><span style="color:#f78c6c;"> mapping list </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> list </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">| [] -> []
</span><span> </span><span style="color:#c792ea;">|</span><span> head </span><span style="color:#c792ea;">::</span><span> tail </span><span style="color:#c792ea;">->
</span><span> mapping head </span><span style="color:#c792ea;">::</span><span> tail
</span><span>
</span><span> </span><span style="color:#c792ea;">let </span><span>mapTail</span><span style="color:#f78c6c;"> mapping list </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> list </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">| [] -> []
</span><span> </span><span style="color:#c792ea;">|</span><span> head </span><span style="color:#c792ea;">::</span><span> tail </span><span style="color:#c792ea;">->
</span><span> </span><span style="color:#c792ea;">let </span><span>tail </span><span style="color:#c792ea;">=</span><span> tail </span><span style="color:#c792ea;">|></span><span> List.map mapping
</span><span> head </span><span style="color:#c792ea;">::</span><span> tail
</span><span>
</span><span style="color:#c792ea;">module private </span><span>Option </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">let </span><span>ofString </span><span style="color:#c792ea;">(</span><span style="color:#f78c6c;">value</span><span style="color:#c792ea;">: </span><span>string</span><span style="color:#c792ea;">) =
</span><span> </span><span style="color:#c792ea;">match</span><span> value </span><span style="color:#c792ea;">|></span><span> String.IsNullOrWhiteSpace </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">| </span><span>true </span><span style="color:#c792ea;">-></span><span> None
</span><span> </span><span style="color:#c792ea;">| </span><span>false </span><span style="color:#c792ea;">-></span><span> Some value
</span><span>
</span><span style="color:#c792ea;">module private </span><span>String </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">let inline </span><span>toUpper </span><span style="color:#c792ea;">(</span><span style="color:#f78c6c;">value</span><span style="color:#c792ea;">: </span><span>string</span><span style="color:#c792ea;">) =
</span><span> value.ToUpperInvariant</span><span style="color:#f78c6c;">()
</span><span>
</span><span> </span><span style="color:#c792ea;">let inline </span><span>toLower </span><span style="color:#c792ea;">(</span><span style="color:#f78c6c;">value</span><span style="color:#c792ea;">: </span><span>string</span><span style="color:#c792ea;">) =
</span><span> value.ToLowerInvariant</span><span style="color:#f78c6c;">()
</span><span>
</span><span style="color:#c792ea;">let private </span><span>capitalize </span><span style="color:#c792ea;">(</span><span style="color:#f78c6c;">value</span><span style="color:#c792ea;">: </span><span>string</span><span style="color:#c792ea;">) =
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> List.ofSeq
</span><span> </span><span style="color:#c792ea;">|></span><span> List.mapHead Char.ToUpper
</span><span> </span><span style="color:#c792ea;">|></span><span> List.mapTail Char.ToLower
</span><span> </span><span style="color:#c792ea;">|></span><span> String.concat </span><span style="color:#89ddff;">""
</span></code></pre>
<p>We're going to need some helper functions in Haskell as well:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">stringToMaybe </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Maybe String
</span><span>stringToMaybe </span><span style="color:#f78c6c;">[] </span><span style="color:#89ddff;">= </span><span>Nothing
</span><span>stringToMaybe value </span><span style="color:#89ddff;">= </span><span>Just value
</span><span>
</span><span style="color:#82aaff;">mapHead </span><span style="color:#c792ea;">::</span><span> (a </span><span style="color:#c792ea;">-> </span><span>a) </span><span style="color:#c792ea;">-></span><span> [a] </span><span style="color:#c792ea;">-></span><span> [a]
</span><span>mapHead _mapping </span><span style="color:#f78c6c;">[] </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">[]
</span><span>mapHead mapping (x</span><span style="color:#89ddff;">:</span><span>xs) </span><span style="color:#89ddff;">=</span><span> mapping x </span><span style="color:#89ddff;">:</span><span> xs
</span><span>
</span><span style="color:#82aaff;">mapTail </span><span style="color:#c792ea;">::</span><span> (a </span><span style="color:#c792ea;">-> </span><span>a) </span><span style="color:#c792ea;">-></span><span> [a] </span><span style="color:#c792ea;">-></span><span> [a]
</span><span>mapTail _mapping </span><span style="color:#f78c6c;">[] </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">[]
</span><span>mapTail mapping (x</span><span style="color:#89ddff;">:</span><span>xs) </span><span style="color:#89ddff;">=</span><span> x </span><span style="color:#89ddff;">:</span><span> map mapping xs
</span><span>
</span><span style="color:#82aaff;">capitalize </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>capitalize </span><span style="color:#89ddff;">=</span><span> mapHead Char</span><span style="color:#89ddff;">.</span><span>toUpper </span><span style="color:#89ddff;">.</span><span> mapTail Char</span><span style="color:#89ddff;">.</span><span>toLower
</span></code></pre>
<h2 id="the-core-algorithm">The Core Algorithm</h2>
<p>With all of the setup done we can now move on to implementing the core library logic.</p>
<p>Our core algorithm is implemented in <code>getWords</code>, which we use to break a string up into a series of words.</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">let private </span><span>isSeparator</span><span style="color:#f78c6c;"> char </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> char </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">| </span><span style="color:#c3e88d;">'_'
</span><span> </span><span style="color:#c792ea;">| </span><span style="color:#c3e88d;">'-'
</span><span> </span><span style="color:#c792ea;">| </span><span style="color:#c3e88d;">' ' </span><span style="color:#c792ea;">-> </span><span>true
</span><span> </span><span style="color:#c792ea;">| _ -> </span><span>false
</span><span>
</span><span style="color:#c792ea;">let private </span><span>isBoundary</span><span style="color:#f78c6c;"> currentChar nextChar </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">let </span><span>isCasingBoundary </span><span style="color:#f78c6c;">() </span><span style="color:#c792ea;">=</span><span> Char.IsLower currentChar </span><span style="color:#c792ea;">&&</span><span> Char.IsUpper nextChar
</span><span>
</span><span> isSeparator nextChar </span><span style="color:#c792ea;">||</span><span> isCasingBoundary </span><span style="color:#f78c6c;">()
</span><span>
</span><span style="color:#c792ea;">let private </span><span>getWords </span><span style="color:#c792ea;">(</span><span style="color:#f78c6c;">value</span><span style="color:#c792ea;">: </span><span>string</span><span style="color:#c792ea;">) =
</span><span> </span><span style="color:#c792ea;">let rec </span><span>getWords</span><span style="color:#f78c6c;">' currentWord words chars </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> chars </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">| [] -></span><span> currentWord </span><span style="color:#c792ea;">::</span><span> words
</span><span> </span><span style="color:#c792ea;">|</span><span> singleChar </span><span style="color:#c792ea;">:: [] ->
</span><span> currentWord </span><span style="color:#c792ea;">+</span><span> string singleChar </span><span style="color:#c792ea;">::</span><span> words
</span><span> </span><span style="color:#c792ea;">|</span><span> currentChar </span><span style="color:#c792ea;">::</span><span> nextChar </span><span style="color:#c792ea;">::</span><span> remainingChars </span><span style="color:#c792ea;">->
</span><span> </span><span style="color:#c792ea;">let </span><span>appendCurrentChar</span><span style="color:#f78c6c;"> word </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">match</span><span> currentChar </span><span style="color:#c792ea;">|></span><span> isSeparator </span><span style="color:#c792ea;">with
</span><span> </span><span style="color:#c792ea;">| </span><span>true </span><span style="color:#c792ea;">-></span><span> word
</span><span> </span><span style="color:#c792ea;">| </span><span>false </span><span style="color:#c792ea;">-></span><span> word </span><span style="color:#c792ea;">+</span><span> string currentChar
</span><span>
</span><span> </span><span style="color:#c792ea;">let </span><span>currentWord,</span><span style="color:#f78c6c;"> words </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">if</span><span> isBoundary currentChar nextChar </span><span style="color:#c792ea;">then
</span><span> </span><span style="color:#89ddff;">""</span><span style="color:#c792ea;">,</span><span> appendCurrentChar currentWord </span><span style="color:#c792ea;">::</span><span> words
</span><span> </span><span style="color:#c792ea;">elif</span><span> currentWord </span><span style="color:#c792ea;">|></span><span> Seq.forall Char.IsUpper </span><span style="color:#c792ea;">&&</span><span> Char.IsUpper currentChar </span><span style="color:#c792ea;">&&</span><span> Char.IsLower nextChar </span><span style="color:#c792ea;">then
</span><span> </span><span style="color:#89ddff;">"" </span><span style="color:#c792ea;">|></span><span> appendCurrentChar</span><span style="color:#c792ea;">,</span><span> currentWord </span><span style="color:#c792ea;">::</span><span> words
</span><span> </span><span style="color:#c792ea;">else
</span><span> currentWord </span><span style="color:#c792ea;">|></span><span> appendCurrentChar</span><span style="color:#c792ea;">,</span><span> words
</span><span>
</span><span> </span><span style="color:#c792ea;">let </span><span>remainingChars </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#c792ea;">if</span><span> not </span><span style="color:#c792ea;">^</span><span> isSeparator nextChar </span><span style="color:#c792ea;">then</span><span> nextChar </span><span style="color:#c792ea;">::</span><span> remainingChars
</span><span> </span><span style="color:#c792ea;">else</span><span> remainingChars
</span><span>
</span><span> getWords' currentWord words remainingChars
</span><span>
</span><span> </span><span style="color:#c792ea;">let </span><span>characters </span><span style="color:#c792ea;">=
</span><span> value.ToCharArray</span><span style="color:#f78c6c;">()
</span><span> </span><span style="color:#c792ea;">|></span><span> List.ofArray
</span><span>
</span><span> getWords' </span><span style="color:#89ddff;">"" </span><span style="color:#c792ea;">[]</span><span> characters
</span><span> </span><span style="color:#c792ea;">|></span><span> List.choose Option.ofString
</span><span> </span><span style="color:#c792ea;">|></span><span> List.rev
</span></code></pre>
<p>Our <code>getWords</code> implementation looks roughly the same in Haskell:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">isSeparator </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Char </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>isSeparator </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">_</span><span style="color:#89ddff;">' = </span><span>True
</span><span>isSeparator </span><span style="color:#89ddff;">'</span><span style="color:#c3e88d;">-</span><span style="color:#89ddff;">' = </span><span>True
</span><span>isSeparator </span><span style="color:#89ddff;">' ' = </span><span>True
</span><span>isSeparator _ </span><span style="color:#89ddff;">= </span><span>False
</span><span>
</span><span style="color:#82aaff;">isBoundary </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">Char </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Char </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">Bool
</span><span>isBoundary _currentChar nextChar
</span><span> </span><span style="color:#89ddff;">|</span><span> isSeparator nextChar </span><span style="color:#89ddff;">= </span><span>True
</span><span>isBoundary currentChar nextChar </span><span style="color:#89ddff;">=
</span><span> Char</span><span style="color:#89ddff;">.</span><span>isLower currentChar && Char</span><span style="color:#89ddff;">.</span><span>isUpper nextChar
</span><span>
</span><span style="color:#82aaff;">getWords </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-></span><span> [</span><span style="font-style:italic;color:#c792ea;">String</span><span>]
</span><span>getWords value </span><span style="color:#89ddff;">=</span><span> reverse </span><span style="color:#89ddff;">$</span><span> catMaybes </span><span style="color:#89ddff;">$</span><span> map stringToMaybe </span><span style="color:#89ddff;">$</span><span> getWords' </span><span style="color:#89ddff;">"" </span><span style="color:#f78c6c;">[]</span><span> value
</span><span> </span><span style="color:#c792ea;">where
</span><span> getWords' currentWord acc </span><span style="color:#f78c6c;">[] </span><span style="color:#89ddff;">=</span><span> currentWord </span><span style="color:#89ddff;">:</span><span> acc
</span><span> getWords' currentWord acc (singleChar</span><span style="color:#89ddff;">:</span><span style="color:#f78c6c;">[]</span><span>) </span><span style="color:#89ddff;">=
</span><span> (currentWord </span><span style="color:#89ddff;">++</span><span> [singleChar]) </span><span style="color:#89ddff;">:</span><span> acc
</span><span> getWords' currentWord acc (currentChar</span><span style="color:#89ddff;">:</span><span>nextChar</span><span style="color:#89ddff;">:</span><span>remainingChars) </span><span style="color:#89ddff;">=
</span><span> </span><span style="color:#c792ea;">let</span><span> appendCurrentChar word </span><span style="color:#89ddff;">=
</span><span> </span><span style="font-style:italic;color:#c792ea;">if</span><span> isSeparator currentChar
</span><span> </span><span style="font-style:italic;color:#c792ea;">then</span><span> word
</span><span> </span><span style="font-style:italic;color:#c792ea;">else</span><span> word </span><span style="color:#89ddff;">++</span><span> [currentChar]
</span><span> (currentWord'</span><span style="color:#89ddff;">,</span><span> acc') </span><span style="color:#89ddff;">=
</span><span> </span><span style="font-style:italic;color:#c792ea;">if</span><span> isBoundary currentChar nextChar
</span><span> </span><span style="font-style:italic;color:#c792ea;">then</span><span> (</span><span style="color:#89ddff;">"",</span><span> appendCurrentChar currentWord </span><span style="color:#89ddff;">:</span><span> acc)
</span><span> </span><span style="font-style:italic;color:#c792ea;">else if</span><span> all Char</span><span style="color:#89ddff;">.</span><span>isUpper currentWord &&
</span><span> Char</span><span style="color:#89ddff;">.</span><span>isUpper currentChar && Char</span><span style="color:#89ddff;">.</span><span>isLower nextChar
</span><span> </span><span style="font-style:italic;color:#c792ea;">then</span><span> (appendCurrentChar </span><span style="color:#89ddff;">"",</span><span> currentWord </span><span style="color:#89ddff;">:</span><span> acc)
</span><span> </span><span style="font-style:italic;color:#c792ea;">else</span><span> (appendCurrentChar currentWord</span><span style="color:#89ddff;">,</span><span> acc)
</span><span> remainingChars' </span><span style="color:#89ddff;">=
</span><span> </span><span style="font-style:italic;color:#c792ea;">if</span><span> not </span><span style="color:#89ddff;">$</span><span> isSeparator nextChar
</span><span> </span><span style="font-style:italic;color:#c792ea;">then</span><span> nextChar </span><span style="color:#89ddff;">:</span><span> remainingChars
</span><span> </span><span style="font-style:italic;color:#c792ea;">else</span><span> remainingChars
</span><span> </span><span style="color:#c792ea;">in</span><span> getWords' currentWord' acc' remainingChars'
</span></code></pre>
<p>I personally find the Haskell version a bit cleaner, especially with Haskell's support for pattern matching in the function definition.</p>
<h2 id="the-public-api">The Public API</h2>
<p>Now that the hard part is done we can finally move onto the actual API of the library itself! This is probably the simplest part of the library, as we just have to define what the casing rules are for each of our desired cases. The beauty of this approach is that if we ever wanted to add a new case it would be super simple to do so.</p>
<p>Here's what our public API looks like in F#:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">let </span><span>toCamelCase</span><span style="color:#f78c6c;"> value </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> getWords
</span><span> </span><span style="color:#c792ea;">|></span><span> List.mapHead String.toLower
</span><span> </span><span style="color:#c792ea;">|></span><span> List.mapTail capitalize
</span><span> </span><span style="color:#c792ea;">|></span><span> String.concat </span><span style="color:#89ddff;">""
</span><span>
</span><span style="color:#c792ea;">let </span><span>toPascalCase</span><span style="color:#f78c6c;"> value </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> getWords
</span><span> </span><span style="color:#c792ea;">|></span><span> List.map capitalize
</span><span> </span><span style="color:#c792ea;">|></span><span> String.concat </span><span style="color:#89ddff;">""
</span><span>
</span><span style="color:#c792ea;">let </span><span>toSnakeCase</span><span style="color:#f78c6c;"> value </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> getWords
</span><span> </span><span style="color:#c792ea;">|></span><span> List.map String.toLower
</span><span> </span><span style="color:#c792ea;">|></span><span> String.concat </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">_</span><span style="color:#89ddff;">"
</span><span>
</span><span style="color:#c792ea;">let </span><span>toScreamingSnakeCase</span><span style="color:#f78c6c;"> value </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> getWords
</span><span> </span><span style="color:#c792ea;">|></span><span> List.map String.toUpper
</span><span> </span><span style="color:#c792ea;">|></span><span> String.concat </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">_</span><span style="color:#89ddff;">"
</span><span>
</span><span style="color:#c792ea;">let </span><span>toKebabCase</span><span style="color:#f78c6c;"> value </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> getWords
</span><span> </span><span style="color:#c792ea;">|></span><span> List.map String.toLower
</span><span> </span><span style="color:#c792ea;">|></span><span> String.concat </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">-</span><span style="color:#89ddff;">"
</span><span>
</span><span style="color:#c792ea;">let </span><span>toTitleCase</span><span style="color:#f78c6c;"> value </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> getWords
</span><span> </span><span style="color:#c792ea;">|></span><span> List.map capitalize
</span><span> </span><span style="color:#c792ea;">|></span><span> String.concat </span><span style="color:#89ddff;">" "
</span></code></pre>
<p>And here's the public API in Haskell:</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#82aaff;">toCamelCase </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>toCamelCase </span><span style="color:#89ddff;">=
</span><span> intercalate </span><span style="color:#89ddff;">"" .</span><span> mapTail capitalize </span><span style="color:#89ddff;">.</span><span> mapHead (map Char</span><span style="color:#89ddff;">.</span><span>toLower) </span><span style="color:#89ddff;">.</span><span> getWords
</span><span>
</span><span style="color:#82aaff;">toPascalCase </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>toPascalCase </span><span style="color:#89ddff;">=</span><span> intercalate </span><span style="color:#89ddff;">"" .</span><span> map capitalize </span><span style="color:#89ddff;">.</span><span> getWords
</span><span>
</span><span style="color:#82aaff;">toSnakeCase </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>toSnakeCase </span><span style="color:#89ddff;">=</span><span> intercalate </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">_</span><span style="color:#89ddff;">" .</span><span> map (map Char</span><span style="color:#89ddff;">.</span><span>toLower) </span><span style="color:#89ddff;">.</span><span> getWords
</span><span>
</span><span style="color:#82aaff;">toScreamingSnakeCase </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>toScreamingSnakeCase </span><span style="color:#89ddff;">=</span><span> intercalate </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">_</span><span style="color:#89ddff;">" .</span><span> map (map Char</span><span style="color:#89ddff;">.</span><span>toUpper) </span><span style="color:#89ddff;">.</span><span> getWords
</span><span>
</span><span style="color:#82aaff;">toKebabCase </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>toKebabCase </span><span style="color:#89ddff;">=</span><span> intercalate </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">-</span><span style="color:#89ddff;">" .</span><span> map (map Char</span><span style="color:#89ddff;">.</span><span>toLower) </span><span style="color:#89ddff;">.</span><span> getWords
</span><span>
</span><span style="color:#82aaff;">toTitleCase </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">String </span><span style="color:#c792ea;">-> </span><span style="font-style:italic;color:#c792ea;">String
</span><span>toTitleCase </span><span style="color:#89ddff;">=</span><span> intercalate </span><span style="color:#89ddff;">" " .</span><span> map capitalize </span><span style="color:#89ddff;">.</span><span> getWords
</span></code></pre>
<p>The one major difference between the F# and Haskell implementations is that the Haskell casing functions are implemented completely in <a href="https://en.wikipedia.org/wiki/Tacit_programming">point-free style</a>.</p>
<blockquote>
<p>It is possible to implement the F# version in point-free style, or the Haskell version using a <a href="http://hackage.haskell.org/package/flow-1.0.19/docs/Flow.html#v:-124--62-">pipeline operator</a>, but I wanted to keep the implementations as idiomatic as possible for their respective languages.</p>
</blockquote>
<h2 id="writing-some-tests">Writing Some Tests</h2>
<p>What kind of developers would we be if we didn't add some unit tests to ensure that everything is working as expected?</p>
<p>For F# we'll be using <a href="https://fsprojects.github.io/FsUnit/">FsUnit</a> and <a href="https://xunit.net/">xUnit</a> to write our tests.</p>
<p>One thing that I really like about xUnit is their support for data-driven tests. In this case we're able to leverage the <code>Theory</code> and <code>InlineData</code> attributes to easily add new test cases without needing to duplicate a bunch of code.</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">namespace </span><span>Tests.Casing
</span><span>
</span><span style="color:#c792ea;">open </span><span>FsUnit</span><span style="color:#89ddff;">.</span><span>Xunit
</span><span style="color:#c792ea;">open </span><span>Xunit
</span><span>
</span><span>module </span><span style="color:#c3e88d;">``</span><span>toCamelCase</span><span style="color:#c3e88d;">`` </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#82aaff;">[<Theory>]
</span><span> </span><span style="color:#82aaff;">[<InlineData</span><span style="color:#c792ea;">(</span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">camelCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">, </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">camelCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">)</span><span style="color:#82aaff;">>]
</span><span> </span><span style="color:#82aaff;">[<InlineData</span><span style="color:#c792ea;">(</span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">PascalCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">, </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">pascalCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">)</span><span style="color:#82aaff;">>]
</span><span> </span><span style="color:#c792ea;">let </span><span>``it correctly converts casing to camelCase`` value expected </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> Casing.toCamelCase
</span><span> </span><span style="color:#c792ea;">|></span><span> should equal expected
</span><span>
</span><span>module </span><span style="color:#c3e88d;">``</span><span>toPascalCase</span><span style="color:#c3e88d;">`` </span><span style="color:#c792ea;">=
</span><span> </span><span style="color:#82aaff;">[<Theory>]
</span><span> </span><span style="color:#82aaff;">[<InlineData</span><span style="color:#c792ea;">(</span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">camelCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">, </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">CamelCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">)</span><span style="color:#82aaff;">>]
</span><span> </span><span style="color:#82aaff;">[<InlineData</span><span style="color:#c792ea;">(</span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">PascalCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">, </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">PascalCase</span><span style="color:#89ddff;">"</span><span style="color:#c792ea;">)</span><span style="color:#82aaff;">>]
</span><span> </span><span style="color:#c792ea;">let </span><span>``it correctly converts casing to PascalCase`` value expected </span><span style="color:#c792ea;">=
</span><span> value
</span><span> </span><span style="color:#c792ea;">|></span><span> Casing.toPascalCase
</span><span> </span><span style="color:#c792ea;">|></span><span> should equal expected
</span></code></pre>
<p>For our Haskell tests we'll be using <a href="https://hspec.github.io/">Hspec</a>.</p>
<p>Hspec doesn't have anything like xUnit's <code>Theory</code> attribute, but as it turns out, we don't need it! Instead we can use a <a href="https://en.wikipedia.org/wiki/Higher-order_function">higher-order function</a> and achieve the same result.</p>
<p>Below you'll see that we've defined a function called <code>makeTest</code> that takes a casing function, an input string, and the expected output string, and uses that to build a test case. The end result is the same—we can write our tests without having to duplicate a bunch of code—only this time we didn't need anything other than the built-in language constructs to do it.</p>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">import </span><span>Casing </span><span style="color:#c792ea;">as </span><span>Casing
</span><span style="color:#c792ea;">import </span><span>Test.Hspec
</span><span>
</span><span>makeTest transform value expected </span><span style="color:#89ddff;">=
</span><span> it (</span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">properly converts </span><span style="color:#89ddff;">\"" ++</span><span> value </span><span style="color:#89ddff;">++ "\"</span><span style="color:#c3e88d;"> to </span><span style="color:#89ddff;">\"" ++</span><span> expected </span><span style="color:#89ddff;">++ "\""</span><span>) </span><span style="color:#89ddff;">$ </span><span style="font-style:italic;color:#c792ea;">do
</span><span> transform value </span><span style="color:#89ddff;">`shouldBe`</span><span> expected
</span><span>
</span><span style="color:#82aaff;">main </span><span style="color:#c792ea;">:: </span><span style="font-style:italic;color:#c792ea;">IO </span><span style="color:#f78c6c;">()
</span><span>main </span><span style="color:#89ddff;">=
</span><span> hspec </span><span style="color:#89ddff;">$ </span><span style="font-style:italic;color:#c792ea;">do
</span><span> describe </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Casing.toCamelCase</span><span style="color:#89ddff;">" $ </span><span style="font-style:italic;color:#c792ea;">do
</span><span> </span><span style="color:#c792ea;">let</span><span> test </span><span style="color:#89ddff;">=</span><span> makeTest Casing</span><span style="color:#89ddff;">.</span><span>toCamelCase
</span><span> test </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">camelCase</span><span style="color:#89ddff;">" "</span><span style="color:#c3e88d;">camelCase</span><span style="color:#89ddff;">"
</span><span> test </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">PascalCase</span><span style="color:#89ddff;">" "</span><span style="color:#c3e88d;">pascalCase</span><span style="color:#89ddff;">"
</span><span> describe </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Casing.toPascalCase</span><span style="color:#89ddff;">" $ </span><span style="font-style:italic;color:#c792ea;">do
</span><span> </span><span style="color:#c792ea;">let</span><span> test </span><span style="color:#89ddff;">=</span><span> makeTest Casing</span><span style="color:#89ddff;">.</span><span>toPascalCase
</span><span> test </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">camelCase</span><span style="color:#89ddff;">" "</span><span style="color:#c3e88d;">CamelCase</span><span style="color:#89ddff;">"
</span><span> test </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">PascalCase</span><span style="color:#89ddff;">" "</span><span style="color:#c3e88d;">PascalCase</span><span style="color:#89ddff;">"
</span></code></pre>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>And that's it! We've implemented our library and added some unit tests to ensure everything is working. Now we can sit back and convert between various cases to our hearts' content.</p>
<p>Interestingly enough, calling our conversion functions looks exactly the same in both F# and Haskell:</p>
<pre data-lang="fs" style="background-color:#212121;color:#eeffff;" class="language-fs "><code class="language-fs" data-lang="fs"><span style="color:#c792ea;">open </span><span>Casing
</span><span>
</span><span>toCamelCase </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Hello World</span><span style="color:#89ddff;">" </span><span style="font-style:italic;color:#4a4a4a;">// "helloWorld"
</span><span>toSnakeCase </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">XMLHttpRequest</span><span style="color:#89ddff;">" </span><span style="font-style:italic;color:#4a4a4a;">// xml_http_request
</span></code></pre>
<pre data-lang="hs" style="background-color:#212121;color:#eeffff;" class="language-hs "><code class="language-hs" data-lang="hs"><span style="color:#c792ea;">import </span><span>Casing
</span><span>
</span><span>toCamelCase </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">Hello World</span><span style="color:#89ddff;">" </span><span style="font-style:italic;color:#4a4a4a;">-- "helloWorld"
</span><span>toSnakeCase </span><span style="color:#89ddff;">"</span><span style="color:#c3e88d;">XMLHttpRequest</span><span style="color:#89ddff;">" </span><span style="font-style:italic;color:#4a4a4a;">-- xml_http_request
</span></code></pre>
<p>I hope you enjoyed this side-by-side tour of implementing a case conversion library in both F# and Haskell!</p>
<p>The Haskell version—affectionality named <a href="https://github.com/maxdeviant/heckin">"heckin"</a>, after its namesake, <a href="https://github.com/withoutboats/heck">heck</a>— is <a href="https://hackage.haskell.org/package/heckin">available on Hackage</a>. So if you find yourself wanting to do case conversion in Haskell, look no further!</p>
<p>I'm still very much a Haskell beginner, so please let me know if you have any tips for how to improve heckin's Haskell implementation.</p>
No More Heroes2019-09-18T01:21:59.693+00:002019-09-18T01:21:59.693+00:00Unknownhttps://maxdeviant.com/posts/2019/no-more-heroes/<p>People always say that you should never meet your heroes. I think that this advice is even more pertinent now than it ever was in the past. Every time you look at the headlines yet another person has become embroiled in some scandal or public debacle.</p>
<p>The recent happenings regarding Richard Stallman have made me think a lot about this. I am sure that there are many people who did—and perhaps still do—look up to Richard Stallman as their personal hero. For the record, I am not one of those people. But there are other people in software who I have made my hero at one point or another. Whether some of those people are worthy of the lofty pedestal I placed them on is debatable.</p>
<p>Over time it has come to my attention that some of my once-called heroes are not as great as I made them out to be. People have problematic views, say insensitive things, and make mistakes constantly. After all, they are just as human as the rest of us.</p>
<p>It is time for us to declare hero bankruptcy. No more heroes.</p>
<p>Instead of elevating other people to extraordinary heights in our minds we should instead elevate the qualities and values that we associate with them. Singling out individual values allows for a greater degree of separation between the things that make us seek out heroes in the first place and the troublesome humanity that those heroes carry along with them. Given enough time, any human heroes will eventually let you down. What won't let you down are the timeless values that made you make someone your hero in the first place.</p>
Longing for Strong Types: Part I2019-05-09T03:49:16.495+00:002019-05-09T03:49:16.495+00:00Unknownhttps://maxdeviant.com/posts/2019/longing-for-strong-types-pt-i/<p>I love strong types. I love having a compiler as my first line of defense against my human fallibility. Being able to ruthlessly refactor code with the knowledge that the compiler will be right there beside me throughout the entire process gives me the confidence to refactor freely. Not only that, but being able to encode business rules into the type system such that <a href="https://twitter.com/yminsky/status/1034947939364425731">illegal states are unrepresentable</a> makes it that much harder for bugs to creep their way into the system.</p>
<p>Conversely, I find myself less and less capable of tolerating weak type systems with each passing day. Writing code in a language with a weak type system makes me feel like I'm fighting an uphill battle to write correct code, especially knowing that there are languages out there that would aid, rather than hinder, me in my quest for correctness.</p>
<h2 id="strong-vs-weak-types">Strong vs Weak Types</h2>
<p>There are a lot of definitions floating around for what it means for a programming language to be "strongly typed" or "weakly typed".</p>
<p>In some cases the distinction can be somewhat subjective:</p>
<blockquote>
<p>Strong typing: A type system that I like and feel comfortable with</p>
<p>Weak typing: A type system that worries me, or makes me feel uncomfortable</p>
<p>— <a href="https://cdsmith.wordpress.com/2011/01/09/an-old-article-i-wrote/">Chris Smith</a></p>
</blockquote>
<p>Rather than formalizing everything and talking about type systems in the abstract, I will use concrete examples to describe what it is that I want out of a type system.</p>
<h2 id="typescript">TypeScript</h2>
<p>I use <a href="https://www.typescriptlang.org/">TypeScript</a> almost every day at work. Building a <a href="https://reactjs.org/">React</a> app, or any other type of JavaScript app, doesn't leave you with too many options in terms of language.</p>
<p>Having so much experience using TypeScript lends itself, naturally, to me having a lot of opinions on it.</p>
<h3 id="the-good">The Good</h3>
<p>For what it is—JavaScript with types bolted on top—I will admit that TypeScript is very good.</p>
<p>The compiler is fast, the tooling is great, and the speed at which the TypeScript team cranks out new features is astounding.</p>
<p>All of this is well and good, but there are many things that make me sad when I'm working with a TypeScript codebase.</p>
<h3 id="the-bad">The Bad</h3>
<p>It's not surprising that TypeScript and I have a bit of a tumultuous relationship, given that one of its design non-goals directly contrasts what I want out of a type system:</p>
<blockquote>
<ol start="3">
<li>Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.</li>
</ol>
<p>— <a href="https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals">TypeScript Design Goals</a></p>
</blockquote>
<p>Not having a sound type system is a non-starter for me as it just removes all of the confidence that I have in the system. The only thing worse than not having a static type system in the first place is having a static type system that I can't trust.</p>
<p>TypeScript does have some rudimentary constructs for things like <a href="https://en.wikipedia.org/wiki/Tagged_union">sum types</a> and <a href="https://en.wikipedia.org/wiki/Pattern_matching">pattern matching</a>, but these are all a poor substitution for their counterparts in languages like Rust or F#.</p>
<h3 id="the-ugly">The Ugly</h3>
<p>One of the worst things about working with TypeScript has got to be managing typings for third-party JavaScript packages. Unfortunately this is just a reality of the JavaScript ecosystem. There are plenty of npm packages that don't have TypeScript typings or have poor or outdated typings. Many times I'll just opt to use a package untyped simply because the typings that exist are so poor that it makes working with the package a nightmare.</p>
<p>The typings problem is something that simply does not exist for a language where static types are first-class within the language. When I install a Rust crate, I don't have to worry about whether or not I will get reliable type information about that crate from the compiler.</p>
<h2 id="where-do-we-go-from-here">Where do we go from here?</h2>
<p>Now that I've aired some of my grievances about weak type systems, specifically TypeScript, what recourse do we have if we want strong types for frontend development?</p>
<p>I'll explore some potential options in the next post.</p>
On the Importance of Technical Excellence2019-02-25T05:00:13.793+00:002019-02-25T05:00:13.793+00:00Unknownhttps://maxdeviant.com/posts/2019/on-the-importance-of-technical-excellence/<p>The dark clouds loom overhead. You feel a chill in the air that sends shivers down your spine. Is this the end of the world?</p>
<p>Not quite; but you do have a software deadline closing in, and you are nowhere near done with your assigned feature. You start to cut corners, your Cherry MX Blues clacking away under your fingertips as you commit code that makes you cringe. You are not alone in this; all around you your teammates follow suit. Some of them know full well what they are doing, while others simply do not know any better.</p>
<p>Despite overwhelming odds you push to prod on schedule. Breathing a sigh of relief, you glance around at the other developers strewn about the room. You have defeated the release this time.</p>
<p>But at what cost?</p>
<hr />
<p>Does this sound familiar? Compromising on code quality in order to meet deadlines? While I am no stranger to the demands of the real world and the compromises that come along with them, I am a firm believer in demanding technical excellence in all aspects of software development.</p>
<h2 id="what-is-technical-excellence">What is "technical excellence"?</h2>
<p>What does "technical excellence" mean? There are a lot of definitions floating around, especially from the agile crowd. Many of these definitions revolve around agile practices, but I want to take a broader look at what technical excellence looks like in practice.</p>
<p>The paper <a href="https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20080014086.pdf">"Technical Excellence: A Requirement for Good Engineering"</a>, published by NASA, lists four main components of technical excellence:</p>
<ul>
<li>Clearly Documented Policies and Procedures</li>
<li>Effective Training and Development</li>
<li>Engineering Excellence</li>
<li>Continuous Communications</li>
</ul>
<p>According to NASA, technical excellence is much more than just writing good code. Engineering excellence is just one facet of technical excellence: being technically excellent involves a cultural shift at the organizational level.</p>
<p>Martin Fowler also sheds some light on what technical excellence means in his talk <a href="https://youtu.be/Avs70dZ3Vlk">"What Does Technical Excellence Look Like?"</a>. The ultimate form of technical excellence that Fowler describes is shortened delivery and feedback cycles. The achievement of this relies on the culmination of the business side of the organization valuing tech and the software development side understanding the needs of the business.</p>
<p>Based on these definitions and my own personal experience, here are the qualities that are required for technical excellence within an organization:</p>
<h3 id="technology-centric">Technology-centric</h3>
<p>In order to achieve technical excellence an organization must be technology-centric. It is important to not confuse being technology-oriented and being technology-centric. After all, all software companies are technology-oriented by nature, but not all software companies are technology-centric.</p>
<p>A technology-centric company values tech at a high level. This should be felt at all levels of the organization, from hiring highly skilled technical people to using technology to solve business problems.</p>
<p>Being technology-centric mandates technical leadership. It is the product owner's responsibility to understand the needs of the business and communicate them effectively with developers, but the weight of meeting those needs falls upon the developers.</p>
<p>Technology-centric organizations know that developers own the development process. It should never be the case that developers are just glorified "code monkeys" whose sole purpose is to implement any feature tossed over the wall from product management.</p>
<blockquote>
<p>It doesn't make sense to hire smart people and then tell them what to do.</p>
<p>We hire smart people so they can tell us what to do.</p>
<p>— Steve Jobs</p>
</blockquote>
<h3 id="clean-code">Clean code</h3>
<p>In the pursuit of technical excellence, one thing that cannot be ignored is the core of what we developers do every day: writing code. This is such an intrinsic part of our daily lives, so it makes sense that it is the largest area in which we can pursue technical excellence.</p>
<p>Technical excellence in code takes the form of writing clean code. This can mean different things to different people, and it can be hard to settle on a common definition of what it means. Despite the subjective nature of the topic, there are plenty of widely agreed-upon standards for writing clean code which should be adhered to.</p>
<p>Things like simplicity, modularity, and loose-coupling are just some of the many ways that developers can keep their code clean. Entire <a href="https://www.oreilly.com/library/view/clean-code/9780136083238/">books</a> have been written on this subject, so I would suggest you go and read them to learn how to write clean, high-quality code.</p>
<h3 id="continuous-learning">Continuous learning</h3>
<p>Technical excellence requires its practitioners to constantly be learning and growing, both as individuals and as a team.</p>
<p>For individuals, this means being willing to learn whatever languages, tools, and patterns are needed in order to perform at their maximum potential. Practices like pair-programming, code reviews, and mentoring are all good ways to share knowledge within a team and help others grow.</p>
<p>Continuous learning for teams means constantly examining our workflows and procedures to identify what works and what doesn't in order to hone our process.</p>
<h3 id="continuous-delivery">Continuous delivery</h3>
<p>Continuous delivery is as much a requirement for technical excellence as it is an outcome.</p>
<p>Achieving continuous delivery requires technical underpinnings as well. Things like version control, automated builds and tests, continuous integration, and automated deployments all work together to make releasing software to users as quick, painless, and hands-off as possible.</p>
<p>Being able to continuously deliver value to the user and gather feedback in short, iterative cycles is what allows software to evolve to meet the needs of the market.</p>
<h3 id="managed-technical-debt">Managed technical debt</h3>
<p>In a perfect world we would always take adequate time to design and implement our software. But this is the real world, which means that sometimes we have to make compromises in order to meet our business goals.</p>
<p>However, when these compromises occur it is critical that we, as software developers, are cognizant of the technical debt we're taking on and fully aware of the future cost of paying off that debt. One thing to note here is that choosing to ignore technical debt is not a valid option. Any debt that is taken on has an intrinsic cost associated with it, and one that will need to be paid off far sooner than anyone realizes.</p>
<p>And always remember that when you do take on technical debt, it had better be clean.</p>
<blockquote>
<p>"A mess is not a technical debt. A mess is just a mess."</p>
<p>— <a href="https://sites.google.com/site/unclebobconsultingllc/a-mess-is-not-a-technical-debt">Uncle Bob Martin</a></p>
</blockquote>
<h3 id="defined-standards-for-quality">Defined standards for quality</h3>
<p>Finally, achieving technical excellence requires defined standards for quality. Without predefined standards for quality it becomes impossible to gauge where your organization stands on the path to technical excellence.</p>
<p>Quality standards should not be developed in a vacuum; they should be based on existing standards of quality that exist in the industry.</p>
<h2 id="why-does-it-matter">Why does it matter?</h2>
<p>Now that I've explained what technical excellence is, let's talk about why it matters.</p>
<h3 id="quality-begets-speed">Quality begets speed</h3>
<p>One of the common traps that organizations fall into is ignoring the importance of producing high quality software. Not just quality in terms of what the user sees when they use the software, but also the internal quality of the software itself.</p>
<p>This may sound a bit counterintuitive at first, especially to business-oriented members of the organization, but time spent on improving code health and quality pays for itself in the long run. Having a healthy codebase makes future changes easier and allows for new features to be added with a linear effect on the maintenance required to keep the codebase in tip-top shape.</p>
<p>On the other hand, adding features with reckless abandon and with little regard for the health of the codebase—while potentially lucrative in the short-term—will ultimately wreak havoc on the ability to add new features in the future.</p>
<p>In order to maintain development speed over the long-term, quality must be at the forefront of everyone's minds, not just an afterthought.</p>
<h3 id="quality-begets-quality">Quality begets quality</h3>
<p>The idea that "quality begets quality" may sound like circular reasoning, but it's the truth. Building software with quality at the forefont will ultimately lead to observable quality for users.</p>
<p>Much like with speed, quality also reduces the number of bugs, which makes it easier to deliver working software to our users.</p>
<h3 id="because-excellency-matters">Because excellency matters</h3>
<p>I was hesitant to include this reason as it is a bit of a hard sell to non-developers. But I think that it cannot be understated that excellence for excellency's sake is a noble pursuit.</p>
<p>Developing software is a craft, no different than carpentry or masonry, and developers should desire to be skilled practitioners of the craft.</p>
<p>This is why I, along with many others, adhere to the Manifesto for Software Craftsmanship:</p>
<blockquote>
<p>As aspiring Software Craftsmen we are raising the bar of professional software development by practicing it and helping others learn the craft. Through this work we have come to value:</p>
<p>Not only working software, but also <strong>well-crafted software</strong></p>
<p>Not only responding to change, but also <strong>steadily adding value</strong></p>
<p>Not only individuals and interactions, but also <strong>a community of professionals</strong></p>
<p>Not only customer collaboration, but also <strong>productive partnerships</strong></p>
<p>That is, in pursuit of the items on the left we have found the items on the right to be indispensable.</p>
<p>— <a href="http://manifesto.softwarecraftsmanship.org/">Manifesto for Software Craftsmanship</a></p>
</blockquote>
<hr />
<p>If you have your own thoughts on technical excellence I would love to hear them, so give me a shout <a href="https://twitter.com/maxdeviant">on Twitter</a>.</p>
New Year, New Site2019-02-23T18:26:28.774+00:002019-02-23T18:26:28.774+00:00Unknownhttps://maxdeviant.com/posts/2019/new-year-new-site/<p>Once again I have returned to my languishing website, this time a little later than usual. The beginning of each new year usually finds me coming back to breathe some life into my site. These spurts are often short-lived, and before long months have passed without any activity. I regret not spending enough time and energy on improving this space.</p>
<p>Part of the problem, I find, is that there is too much friction in maintaining a site. Writing is hard enough as it is without me having to juggle everything else that comes with maintaining a website as well. To that end, I decided to scrap it all and start fresh.</p>
<p>My site is now powered by <a href="https://www.getzola.org/">Zola</a>, a static site generator written in <a href="https://www.rust-lang.org/">Rust</a>. It is a single binary, which means barely anything to maintain. This is a breath of fresh air after dealing with <code>npm</code> packages for so long. Zola is also <em>blazing</em> fast, which is something that I value very much in my tools these days. This whole site, small as it is, gets generated in under 300ms.</p>
<p>I still use <a href="https://zeit.co/now">Now</a> for hosting, because it's just so dead-simple to use. I will probably do a small write-up on how to get a Zola site up and running on Now, just in case there is anyone else out there looking for a no-hassle static site setup.</p>
<p>With the infrastructure concerns mostly out of the way, I hope that I can spend more time focusing on my writing.</p>
2:15 am2018-03-05T07:15:21+00:002018-03-05T07:15:21+00:00Unknownhttps://maxdeviant.com/tales/2-15-am/<p>How long do I have to endure this constant state of internal torment?</p>
<p>I feel my body and mind tearing themselves to pieces.</p>
<p>My desire for rest is acutely felt, but my mind refuses to release me to it.</p>
<p>I feel that life is passing me by and I am but a bystander, watching it all flit away before my very eyes.</p>
<p>I can’t bring myself to get out of bed in the mornings and face all that which feels immeasurably meaningless.</p>
<p>I constantly wonder if anyone else feels this way. Maybe everyone does. And if they do, what is the secret that they possess that allows them to overcome these feelings?</p>
<p>I wish there were someone who could understand what I’m going through and help me through it.</p>
<p>But maybe that person doesn’t exist.</p>
<p>Maybe it’s just me.</p>
<p>Ever me.</p>
<p>Only me.</p>
<p>Lonely me.</p>
<p>Why me?</p>
February Status Report2018-03-01T04:21:49.562+00:002018-03-01T04:21:49.562+00:00Unknownhttps://maxdeviant.com/posts/2018/february-status-report/<p>After <a href="https://twitter.com/cblgh">cblgh</a> <a href="https://twitter.com/cblgh/status/968572042198093824">tweeted</a> at me, I figured now would be a good time to give a status report on my life and progress towards my various goals.</p>
<h2 id="ongoing-goals">Ongoing Goals</h2>
<p>As a refresher, these are the things that I outlined that I wanted to accomplish this year:</p>
<ul>
<li>Work less, achieve more</li>
<li>No alcohol</li>
<li>Read more</li>
<li>Write more</li>
<li>Practice my Mandarin</li>
<li>Produce 1 song/week</li>
<li>Continue to make one thing every day</li>
</ul>
<p>Here is how I am doing on these various things:</p>
<blockquote>
<p>Work less, achieve more</p>
</blockquote>
<p>I have been trying to maintain a better work-life balance over the past two months. We have a lot going on at work, so this has been hard. But the good news is that I am not feeling as burnt-out as I was at the tail end of last year.</p>
<blockquote>
<p>No alcohol</p>
</blockquote>
<p>I have not had any alcohol this year, nor have I had the desire to. I will say that I have gotten some pretty weird looks from people when I have told them that I have stopped drinking for this year.</p>
<blockquote>
<p>Read more</p>
</blockquote>
<p>I have a number of books that are waiting to be read, but have not made progress on any of them so far. I will need to schedule dedicated time to read on a daily or weekly basis and then stick to that schedule.</p>
<blockquote>
<p>Write more</p>
</blockquote>
<p>If I am writing this post right now, does that count? Writing is something that I find hard to sit down and do. I think one thing that might help is brainstorming some ideas at the beginning of the month and then writing them over the course of it.</p>
<blockquote>
<p>Practice my Mandarin</p>
</blockquote>
<p>Haven't done any work towards this so far. Again, I am going to start scheduling time slots to practice my language skills.</p>
<blockquote>
<p>Produce 1 song/week</p>
</blockquote>
<p>This week is week nine of <a href="https://weeklybeats.com/maxdeviant">WeeklyBeats</a>. I have had a lot of fun with this project in particular so far. There is something deeply satisfying about making music, even moreso than working in visual mediums. As the project progresses I continue to learn more about Ableton Live and music production in general, and aim to get better with each week.</p>
<p>I am also excited to announce that I am now published on Spotify! Be sure to follow my <a href="https://open.spotify.com/user/maxdeviant/playlist/6USFB7FubW7OtFzICTEjRD">WeeklyBeats 2018 playlist</a> to keep up-to-date on my new tracks as they are released.</p>
<blockquote>
<p>Continue to make one thing every day</p>
</blockquote>
<p>My <a href="https://are.na/marshall-bowers/everydays">everydays</a> project is continuing along just fine. Today is day 276, and I find it hard to believe that I have already made it this far. When I first started the project I was worried that I might not even make it to thirty consecutive days.</p>
<p>One thing I have noticed lately is that I am more hard-pressed for ideas. Going into March I will be looking for more sources of inspiration that I can use to stir my creativity and inspire more of the pieces that I create.</p>
<h2 id="new-developments">New Developments</h2>
<h3 id="work-in-progress">Work in Progress</h3>
<p>I recently joined the community over at <a href="https://wip.chat/">WIP</a>. So far it has been a great motivator to help me get things done every day. I would like to start actually shipping more over there and practice building stuff in the open.</p>
<h3 id="other-projects">Other Projects</h3>
<p>I have a number of other early-stage projects that I am working on, both by myself and as a collaboration with others. Hopefully one or more of them will be ready to share in the near future!</p>
<h2 id="personal">Personal</h2>
<p>I am still soldiering through a lot of personal issues dealing with motivation, self-doubt, and caring for my physical and mental well-being. The past couple of months have been a hard-fought battle against myself, but it feels like I am starting to gain ground.</p>
Erase the Past2018-02-11T08:30:36+00:002018-02-11T08:30:36+00:00Unknownhttps://maxdeviant.com/tales/erase-the-past/<p>Erase the past.</p>
<p>Sear it from your memory.</p>
<p>Tear the pages from your annals and set them alight.</p>
<p>Remove your rose-tinted glasses and crush them underfoot.</p>
<p>Look forward.</p>
<p>The future is bright.</p>
<p>Do not spend another nanosecond dwelling on yesterday.</p>
<p>And don’t you even think about looking back.</p>
<p>Lest you return to the cesspit you left behind.</p>
Fleeting2018-01-28T08:20:45.563+00:002018-01-28T08:20:45.563+00:00Unknownhttps://maxdeviant.com/posts/2018/fleeting/<p>All my life I have been counting time. Back in high school I would count my time in hours, or even fractions of an hour. "Half an hour left of this class period." "Two more hours until school is out." In university I started counting in weeks. "This project is due in week six of the semester." "Only one more week of classes left." "Ugh, finals week." In the workplace I measure time in "man-days", but often from the larger perspective of months or quarters.</p>
<p>The first month of this year has left me increasingly aware of the fleeting nature of time, and subsequently life. The days pass by so quickly and often I will wake up on a Saturday morning and wonder where the past week went. Even on a daily basis I have noticed just how quickly five, ten, fifteen minutes can disappear in the blink of an eye, to the point where it pains me to see those precious minutes slipping away.</p>
<p>Today I decided I need to look at my life from 10,000 feet. It is so easy to get engrossed in day-to-day circumstances and lose sight of what truly matters.</p>
<p>As part of this, I have started building some tools to help me visualize my life.</p>
<p>The first visualization is my life in <a href="/weeks">weeks</a>. Each cell represents one week of my life. The filled cells are weeks in the past, and the empty cells represent weeks in the future. While I am certainly <a href="https://waitbutwhy.com/2014/05/life-weeks.html">not</a> <a href="http://wiki.xxiivv.com/#death">the</a> <a href="https://weeks.cblgh.org/">first</a> person to do this, making it personal makes the weight more palpable.</p>
<blockquote>
<p>We grow older and we sigh,</p>
<p>Older still, and then we die!</p>
<p>— <em>Fin de Siècle</em>, Edmund Vance Cooke</p>
</blockquote>
<p>The other visualization I put together is a set of timers for visualizing my <a href="/age">age</a>. There are currently three, although I may add more as new goals or miletones come to mind. The first timer shows how long I have lived thus far. This forces me to reflect on what I have accomplished thus far and whether it has been worth my while. The second counts down to my next birthday to help me stay focused on improving my present self. And the third counts down to my death. I have it set to ninety years of age as a ballpark figure just so I have something to shoot for. The death timer serves to inspire me to work my hardest until time is up.</p>
<p>There will be more work done on these and other tools as I continue to deepen my understanding of myself and how I view the human experience.</p>
<blockquote>
<p>I am learning everyday to allow the space between where I am and where I want to be to inspire me and not terrify me.</p>
<p>— Tracee Ellis Ross</p>
</blockquote>
<p>I find myself still very much terrified by what the future holds. The fear of wasting the talents and opportunities I have been given and not living up to my potential can be crippling at times. As the month of January draws to a close, it is my hope that focusing on the fleetingness of life will allow me to identify which areas of my life need a shift in focus.</p>
Default Dead or Alive2017-01-31T05:00:00+00:002017-01-31T05:00:00+00:00Unknownhttps://maxdeviant.com/posts/2017/default-dead-or-alive/<p>Am I living default dead or default alive?</p>
<p>The methodology behind figuring this out is quite simple: if I take any one moment in my everyday life as a data point, would I consider myself dead or alive at that point? Now do that for a second data point. And a third. Now a fourth. At any given moment, what are the odds that I am dead or alive?</p>
<p>I do not mean this in the biological sense. There are scientific means of knowing whether a human being is physically dead. What I am referring to is my purpose in life and how I view myself:</p>
<ul>
<li>Am I being the individual I aspire to be?</li>
<li>Am I doing the things that I want to do?</li>
<li>Am I making an impact on the world?</li>
<li>Am I making the most of every fleeting second I have been given?</li>
</ul>
<p>These questions may vary from person to person, but I believe that most would share a common theme. Humanity exists as much more than mere flesh and bone, and this manifests itself in desires that elude the grasp of physicality. Finding purpose and achieving fulfillment are not quantifiable metrics, which makes it all the more difficult to know whether they have been achieved.</p>
<p>Asking myself these questions is not an easy exercise. Quite honestly, sometimes I am terrified of asking them only to realize that I am default dead. Sometimes the fear is so crippling that I just ignore the questions. I pretend to not know the answer to avoid coming to terms with my own life being default dead.</p>
<p>The speed at which the first month of 2017 has drawn to a close gives me new perspective on this. Life moves at a breakneck pace, and it certainly shows no signs of slowing down. Now more than ever I see the importance of living default alive. I have to take a step back and look at these past thirty-odd days through a critical lens and ask myself how many of those days I spent more alive than dead. The answer to that question will impact the way I live tomorrow, next week, next month, and until there are no more days left to live.</p>
<p>Life is too short to be default dead. Choose to live life default alive.</p>
In Hindsight2016-12-31T05:00:00+00:002016-12-31T05:00:00+00:00Unknownhttps://maxdeviant.com/posts/2016/in-hindsight/<p>This year has really been one hell of a ride. I know the fun thing to do these days is bash on 2016 and all of the bad that happened, but I find it hard to categorize the year as a whole as "good" or "bad". For me, this year has been a mixed bag. Lots of ups, downs, and everything in between. Regardless of anything else, I am happy with the progress I have made in all areas of my life, but I know it is still just a stepping stone towards the individual I am continually striving to become.</p>
<h2 id="year-of-react">Year of React</h2>
<p>The most exciting thing about this year was I finally started using <a href="https://facebook.github.io/react/">React</a>.</p>
<p>Learning and using React has been on the list of things to do for quite a long time. I had played around with it a bit previously, but it wasn't until I dived in head-first at work that I truly started to internalize and love it.</p>
<p>The funny thing about my love for React is that it is not the library itself that I love. While React is great, it still has its quirks and trade-offs, just like any other library or framework. However, the way React forces you to think about your user interfaces is incredibly powerful, and that knowledge will outlive any frontend library.</p>
<h2 id="graduation">Graduation</h2>
<p>2016 marks the completion of a major milestone for me: finishing my college degree. I finished my remaining classes in the spring while continuing to work full-time at Gravic. While not my most challenging semester course-wise, I still had a grueling time finishing this last leg of my college journey.</p>
<p>I am happy to say that I finally fulfilled my long-time goal of achieving a 4.0 GPA for the semester. This is something that I had yet to do, and I found it only fitting that it was achieved during my last semester of my undergraduate degree, and, perhaps, my last semester of higher education ever.</p>
<p>I also graduated cum laude, of which I am fairly proud. There were times where I thought that this would be unachievable, but I fought to get this one last achievement before my time as a college student was up.</p>
<p>My graduation ceremony itself felt rather perfunctory. It took place on a rainy Saturday, and was held outside despite the weather. The upside of this was that all the pomp and circumstance was skipped, and we went right from the guest speaker to the presentation of diplomas.</p>
<p>Honestly, I am just glad to be done.</p>
<h2 id="ambitions">Ambitions</h2>
<p>One year ago I set out to do a lot of things in 2016. Looking back, I think it could be said that I bit off a bit more than I could chew. However, I do think that there is something to be said for not being afraid to try in the first place.</p>
<p>I put my best foot forward in 2016 and here's how I did:</p>
<h3 id="successes">Successes</h3>
<blockquote>
<p>Spend more meaningful time with my friends</p>
</blockquote>
<p>This really came together in the later half of the year. I started attending game night with my friends on Wednesday evenings, where we get together and drink and play board games. I find that having it right in the center of my week is a good way to break the week up.</p>
<p>Some of our favorite games to play include <a href="https://en.wikipedia.org/wiki/Bohnanza">Bohnanza</a> and <a href="https://en.wikipedia.org/wiki/Zombicide">Zombicide</a>. Bohnanza, in particular, is a great game to play with a bunch of people. The rules are fairly simple, and most people can pick it up within the first round. There are also a bunch of expansions for Bohnanza, so people who already know the base game can incorporate new systems to keep the game interesting.</p>
<blockquote>
<p>Start weightlifting and tone my physique</p>
</blockquote>
<p>I started my first round of P90X3 this year. <a href="https://twitter.com/maxdeviant/status/774820003103313920">This thread</a> details how I did each day and how I felt post-workout.</p>
<p>I got about halfway through the program before my crazy work hours and seasonal affective disorder got the better of me. I plan on getting back into the habit moving forward. The weeks where I was working out were the best that I have ever felt.</p>
<blockquote>
<p>Get my first big tattoo</p>
</blockquote>
<p>In February of this year I decided it was time for me to get my first big tattoo. I already had three tattoos, but all of them were fairly small pieces. I fixed this by getting a lion's head done on my chest.</p>
<p>The sitting was three hours, broken up into three roughly fourty-five minute chunks, with short breaks in between. The pain was not unbearable, aside from the small part of the tattoo that goes up onto my clavicle.</p>
<p><a href="https://www.instagram.com/p/BCB3rxLB7f7/">Here's</a> the picture right after we finished up.</p>
<p>My artist, Adam — who has done all of my pieces so far — did an amazing job on this one. I look forward to working with him more in the coming years.</p>
<blockquote>
<p>Drink less alcohol</p>
</blockquote>
<p>After last year, I decided it was time to drastically cut back on my alcohol intake.</p>
<p>This did not prove too difficult for me. I cut out hard liquor entirely, with the exception of during the holidays. My beer and wine intake was also toned down. These days I will have a couple of beers with my friends at game night each Wednesday or a beer with my meal when I go out to eat.</p>
<blockquote>
<p>Graduate college with honors</p>
</blockquote>
<p>I did it!</p>
<blockquote>
<p>Push 10k Git commits</p>
</blockquote>
<p>My Git commits for this year came in at 7,642, with 6,327 of those being at work. While this is short of my 10k goal, I would still consider this a success. My work commits are up by 3,133, and overall number of commits has almost doubled.</p>
<p>The interesting thing to note is that while my work commits rose by a decent amount, my commits done in my spare time remained about the same. I find this particularly heartening, as it appears that I am still as active with my personal projects as I was last year.</p>
<blockquote>
<p>Automate more (especially at work)</p>
</blockquote>
<p>This one gets a partial check mark next to it.
At my behest, we have been working to automate more at work. We started using <a href="https://octopus.com/">Octopus</a> for managing our cloud deployments, and that has done a lot to cut down on manual processes. We are by no means fully automated yet, but I think we have taken a good first step in this direction.</p>
<h3 id="failures">Failures</h3>
<p>I won't go into these in much detail, but these are the things I wanted to do, but failed at in one way or another:</p>
<ul>
<li>Develop more personal friendships with those I admire
<ul>
<li>I still feel like my interactions with the people I know solely online are very surface-level, and I'm still trying to figure out how to take them deeper</li>
</ul>
</li>
<li>Buy a bike and start riding</li>
<li>Ship one of my (numerous) personal projects</li>
<li>Read 12 books
<ul>
<li>This one gets a big, fat zero</li>
</ul>
</li>
<li>Write 24 blog posts
<ul>
<li>Dropped the ball on this one too, given that this is my first post all year</li>
</ul>
</li>
<li>Start using a solid backup solution</li>
<li>Make it to Platinum in League of Legends</li>
<li>Leave the country (for a little while)
<ul>
<li>A lack of time and insufficient funds kept me landlocked in the US of A in 2016</li>
</ul>
</li>
<li>Minimize my possessions
<ul>
<li>My life still has far too much clutter in it</li>
</ul>
</li>
<li>Relearn and practice my Mandarin</li>
</ul>
<h2 id="inspirations">Inspirations</h2>
<p>The list of people who have inspired me in some way this year is rather extensive. All of them have helped shape me into the person that I am. One of the common threads between all of these individuals is their passion for what they do and the cordiality with which they interact with others.</p>
<p>If any of you ever find yourself in the Philadelphia area, I would love to meet up and buy you a meal or a drink.</p>
<h3 id="dan-abramov">Dan Abramov</h3>
<p><a href="https://twitter.com/dan_abramov">@dan_abramov</a></p>
<p><a href="https://medium.com/@dan_abramov">Writing</a></p>
<p>I started following Dan on Twitter when I began learning React and <a href="http://redux.js.org/">Redux</a>. Unfortunately, this was a few months prior to <a href="https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367">"You Might Not Need Redux"</a>, so I went through the process of learning and implementing Redux before realizing that it was not needed for our team's use case. Thankfully, it was pretty simple to swap out for <a href="https://mobxjs.github.io/mobx/">MobX</a> later, but that's a subject for another time.</p>
<p>It became obvious fairly quickly that I had made the right choice in following Dan on Twitter. He always seems to have a unique perspective on everything, and always seems to be fighting dogma around every corner. Dan was really the first instance where I saw the author of a library actively <em>discouraging</em> people from using it, if it was not the right choice.</p>
<p>Dan cares a lot about empowering developers to choose the right tools to get the job done. I admire his commitment to developer experience and helping lower the barrier of entry for using great tools like React, Babel, and Webpack. <a href="https://github.com/facebookincubator/create-react-app">Create React App</a> stands as a testament to this, and I am looking forward to what Dan puts his hands to in 2017.</p>
<h3 id="guillermo-rauch">Guillermo Rauch</h3>
<p><a href="https://twitter.com/rauchg">@rauchg</a></p>
<p><a href="http://rauchg.com/essays/">Writing</a></p>
<p>Something that you notice pretty quickly about Guillermo is his vision and passion. He is currently leading the team over at <a href="https://zeit.co/">Zeit</a>, and all the stuff they are building reflects that.</p>
<p><a href="https://zeit.co/now/">now</a>, <a href="https://hyper.is/">Hyper</a>, and <a href="https://github.com/zeit/next.js">Next.js</a> are all really cool projects, and I would highly recommend checking them out. Each of them has an elegance to its design, be it visual or programmatic. One of the things that I think Guillermo excels at is finding beauty in simplicity, and then presenting that beauty in such a way that others can see it easily.</p>
<p>I find Guillermo's essay on <a href="http://rauchg.com/2015/pure-ui/">"Pure UI"</a> particularly influential. It breaks out some of the incredibly powerful concepts present in React and distills them into ideas that are not tied to a particular technology.</p>
<p>In my interactions with Guillermo, I have always found him to be extremely friendly and personable. I hope that I get to meet him face-to-face sometime in the near future.</p>
<h3 id="tobias-van-schneider">Tobias van Schneider</h3>
<p><a href="https://twitter.com/vanschneider">@vanschneider</a></p>
<p><a href="http://www.vanschneider.com/blog/">Writing</a></p>
<p>Tobias has influenced me in more ways than I care to count. I look forward to his <a href="http://www.vanschneider.com/signup/">weekly emails</a> which go out (usually) on Mondays. It's a great way to start off the week with some insight on a particular topic.</p>
<p>Some of my favorite articles by Tobias are <a href="http://www.vanschneider.com/perfectionism-is-killing-your-creativity/">"Perfectionism Ruined my Productivity"</a>, <a href="http://www.vanschneider.com/why-i-write/">"Why I Write"</a>, and <a href="http://www.vanschneider.com/worklife-balance-is-bullsht/">"Work/Life Balance Is Bullsh*t"</a>.</p>
<p>Interestingly enough, we both have <a href="https://twitter.com/maxdeviant/status/771687596397387777">tattoos of sacred geometry</a> on our left forearms.</p>
<h3 id="ryan-florence">Ryan Florence</h3>
<p><a href="https://twitter.com/ryanflorence">@ryanflorence</a></p>
<p>When we started using React at work, I had settled upon <a href="https://github.com/ReactTraining/react-router">React Router</a> as our routing solution, and started following Ryan on Twitter as a result.</p>
<p>Watching him and Michael Jackson work on React Router v4 has been pretty exciting. I think the emphasis on declarativity and composability is the way that routing in React was meant to be. And the idea that "if you know React, you know React Router" is great for those who are just diving into React.</p>
<p>I admire his commitment to keeping things simple and easy to learn, despite facing criticism from those who are afraid of library churn.</p>
<p>I also have Ryan to thank for this year's foray into personal fitness. His daily P90X3 tweets gave me the push I needed to start the program for myself.</p>
<h3 id="ken-wheeler">Ken Wheeler</h3>
<p><a href="https://twitter.com/ken_wheeler">@ken_wheeler</a></p>
<p>Ken is a real character. I appreciate that, like mine, his Twitter is a mashup of seriousness and ridiculousness. This guy is not afraid to speak his mind, but I suppose that could have something to do with his tree trunks for arms.
Ken serves as my reminder that I don't have to take myself so seriously all of the time. Sometimes it's good to just let loose and live a little.</p>
<p>Also, <a href="https://github.com/FormidableLabs/react-music">React Music</a> is straight dope.</p>
<h3 id="adam-morse">Adam Morse</h3>
<p><a href="https://twitter.com/mrmrs_">@mrmrs_</a></p>
<p><a href="http://mrmrs.io/writing/">Writing</a></p>
<p>I have learned a <strong>ton</strong> from Adam's writings on design and CSS. As someone who has learned these as somewhat ancillary knowledge to programming, his writings have provided an excellent jumping-off point for me as I go back to bolster my understanding of these topics.</p>
<p>Another essay of his, <a href="http://mrmrs.io/writing/2016/04/26/dogma/">"Growing Up With Dogma"</a>, has given me a new perspective on dealing with the dogma that I face at work.</p>
<p>His emphasis on performance, responsiveness, and accessibility inspires me to do the same with my own work.</p>
<p>He is also the reason I am going to learn <a href="http://www.vim.org/">vim</a> in 2017.</p>
<h3 id="brent-jackson">Brent Jackson</h3>
<p><a href="https://twitter.com/jxnblk">@jxnblk</a></p>
<p><a href="http://jxnblk.com/writing/">Writing</a></p>
<p>I think of Brent as the yang to Adam Morse's yin. While still focused on design, Brent does a great job of tying it in with React.</p>
<p>He has created a bunch of UI libraries for React, including <a href="http://jxnblk.com/rebass/">Rebass</a> and <a href="http://jxnblk.com/cxs/">CXS</a>.</p>
<p>His essay on <a href="http://jxnblk.com/writing/posts/patterns-for-style-composition-in-react/">"Patterns for Style Composition in React"</a> has been foundational to my understanding of styling React components.</p>
<h3 id="brian-lonsdorf">Brian Lonsdorf</h3>
<p><a href="https://twitter.com/drboolean">@drboolean</a></p>
<p><a href="https://medium.com/@drboolean">Writing</a></p>
<p>My first encounter with Brian was in his React Rally 2016 talk <a href="https://www.youtube.com/watch?v=SfWR3dKnFIo">"Oh Composable World"</a>. I watched it, and hit that Twitter follow button immediately afterwards.</p>
<p>Functional programming and the raw, gritty internals of it excite me, and I think that Brian shares my excitement for these topics.</p>
<p>Brian has written a book called <a href="https://drboolean.gitbooks.io/mostly-adequate-guide/content/">"Professor Frisby's Mostly Adequate Guide to Functional Programming"</a>. While I have yet to read it, it seems like it will further my knowledge of functional programming quite a bit.</p>
<p>He also has an excellent <a href="https://egghead.io/">egghead.io</a> course titled <a href="https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript">"Professor Frisby Introduces Composable Functional JavaScript"</a>, which is a great introduction to composable, functional JavaScript.</p>
<h3 id="pieter-levels">Pieter Levels</h3>
<p><a href="https://twitter.com/levelsio">@levelsio</a></p>
<p><a href="https://levels.io/index/">Writing</a></p>
<p>Pieter Levels has got to be the most productive person I know of. If anybody embodies the "done is better than perfect" mantra, it's him.</p>
<p>Pieter currently works on <a href="https://nomadlist.com/">Nomad List</a>, and a whole bunch of other side projects. He also launched <a href="https://levels.io/12-startups-12-months/">12 startups in 12 months</a>.</p>
<p>While I don't always agree with Pieter's approach to development, I admire his ability to get things done, and build products that people want to use. He is not inhibited by perfectionism or "best practices"; he just gets things done.</p>
<h2 id="resolutions">Resolutions</h2>
<ul>
<li>Learn to <a href="http://www.manifestoproject.it/bre-pettis-and-kio-stark/">GET. SHIT. DONE.</a></li>
<li>Less dogmatism, more pragmatism</li>
<li>Keep a better <a href="http://bulletjournal.com/">journal</a></li>
<li>Keep a <a href="https://www.youneedabudget.com/">budget</a></li>
<li>Learn <a href="http://www.vim.org/">vim</a></li>
<li>Read at least 12 books</li>
<li>Write more</li>
<li>Finish a full round of P90X3</li>
<li>Organize my bookmarks</li>
<li>Plan and start a tattoo sleeve</li>
<li><a href="http://www.theminimalists.com/288/">Minimize my possessions</a></li>
</ul>
<h2 id="kill-perfection">Kill Perfection</h2>
<p>I have a confession to make: I'm a perfectionist.</p>
<p>Another confession: it's <a href="http://www.vanschneider.com/perfectionism-is-killing-your-creativity/">murdering my productivity</a>.</p>
<p>Tobias mentions this quote in his "Perfectionism Killed my Productivity":</p>
<blockquote>
<p>If your fidelity to perfectionism is too high, you never do anything.</p>
<p>— David Foster Wallace</p>
</blockquote>
<p>This really resonated with me. Sometimes I get so caught up with doing things "right", that it becomes paralyzing. I can't take action until I figure out what the "right" way is.</p>
<p>But what if there is no right way?</p>
<p>If there is anything I learned in 2016, it's that no one really knows what they are doing. Knowing is not the point. Doing is the point. If you succeed, great. If you fail, then you learn, and are one step closer to success.</p>
<p>In 2017 I will kill my perfection.</p>
<p>In 2017 I will slay my dogma.</p>
<p>In 2017 I will forgo my doubts.</p>
Starting Fresh2016-12-27T23:49:00+00:002016-12-27T23:49:00+00:00Unknownhttps://maxdeviant.com/tales/starting-fresh/<p>Do you ever just want to start fresh? Purge the cancer from your life that has just been eating away at you for as long as you can remember? A longing to remove the apathy towards everything and the lack of motivation to change?</p>
<p>A drastic and abrupt change. Waking up one morning and deciding that you're tired of living in a rut. You know you have to get out, and all that's needed is action.</p>
<p>You hop out of bed on the side opposite your usual. You wake up an hour early to see the sun as it crests over the trees rather than rotting in bed, anxiously hoping the beams don't cross your pillowcase.</p>
<p>You walk into the shower as it spurts frigid cold water onto your skin. The icy veins it sends through your system a relish compared to the warm complacency you are so used to bathing in.</p>
<p>Hair gets parted the opposite way. You style it up instead of down, showing off parts of your face that you forgot existed. Clothes are pulled on, fresh and laundered. If looks could kill everyone in existence would be dead. And how you used to wish they were. But not today. Because today the past is gone, and you're about to set forth in the spirit of not caring. Going out and not spending a second thinking about those around you.</p>
<p>This is all for you and you alone.</p>
<p>Tearing through your belongings you choose what matters most and leave the rest behind. It's drastic, but you tear down fixtures, smash your idols of old and leave them in heaping piles. The old is thrown into garbage bags, filled and knotted without a second thought. All that remains is what the new you requires.</p>
<p>People stare at you and wonder if you've gone completely mad. It is quite possible. But if this is madness then yesterday was the last day you will ever be sane again. This new bliss that envelops you is so refreshing, and the sensation only grows with each new change.</p>
<p>And at the end of it all you hope you'll be happy and content. The vices of yesterday are but a memory. A memory which will soon be thrown out, leaving no trace of the old self.</p>
Planet A2015-12-31T05:00:00+00:002015-12-31T05:00:00+00:00Unknownhttps://maxdeviant.com/posts/2015/planet-a/<p>How time flies. It feels like not too long ago that I found myself writing about 2014. In comparison, 2015 as a whole has been pretty dreadful. Based on some of the other musings I have read on the internet, it would seem I am not alone in thinking this. Now without further ado, here is my 2015 year in review.</p>
<h2 id="love-loss-and-awful-grades">Love, Loss, and Awful Grades</h2>
<p>While I traditionally refrain from airing my personal life on the open internet, I think there is definitely something to be said for being open with one's life. Everyone has something they are currently dealing with, and it is my hope that keeping a record of my experiences could serve as an encouragement for anyone else going through a similarly dark time.</p>
<p>The tail end of this year found me a complete emotional wreck. My girlfriend of two years left me on July 24th, and I spiralled downhill from there. Coupled with what I consider my toughest academic semester yet, things were pretty terrible for a few months. While the fall semester started out alright, by mid-November my <a href="https://en.wikipedia.org/wiki/Seasonal_affective_disorder">SAD</a> had begun to set in, and my grades began to suffer as I sank into depression. Being newly 21 and severely depressed, I started drinking pretty heavily as my way of coping with everything.</p>
<p>Ultimately and thankfully, I survived. In what I consider to be an act of Divine Providence, I was able to pass all of my classes, and maintain a 3.44 overall GPA.</p>
<p>I also rekindled some old friendships that had been severely neglected over the course of my previous romantic relationship. Two years of focusing solely on one person resulted in me becoming rather narrow-minded, and, much to my regret, shutting out some extraordinary people in the process. Keeping balance in relationships is an art, and one that I will definitely be keeping in mind the next time I find myself romantically involved with someone.</p>
<h2 id="the-start-of-a-career">The Start of a Career</h2>
<p>Probably the biggest and best thing that has happened to me this year is that I got my first salaried job. After a short (and somewhat wishful) stint with a recruiter at Google, I accepted an offer from Gravic for a full-time developer position. I have been interning as a programmer at <a href="http://gravic.com/">Gravic, Inc.</a> since May of 2013, and in December I started as a full-time Programmer Associate. This is a major milestone in my life, and I could not be happier about this opportunity.</p>
<h2 id="stats">Stats</h2>
<h3 id="code">Code</h3>
<p>I continued to write a lot of code this year, but I found myself working less on personal projects and spending more time coding for school and work. I pushed a total of 4,280 commits over the course of the year, with a peak commit streak of 14 days. Of those 4,280 commits, 3,194 were done at work, meaning I spend 74.6% of my coding time at work. I am still a little unsure how I feel about the transition to more commits done at work. I love my job, and I have been working on some pretty cool stuff, but the fact that I cannot share the work with others is a little disheartening. This is one area I will definitely be watching closely as I move into 2016.</p>
<h3 id="music">Music</h3>
<p>I listened to a lot of music in 2015. By <a href="https://yearinmusic.spotify.com/en-US">Spotify's Year in Music</a> count, I listened to 102,000 minutes of music this year, which translates to 1,695 hours or 71 days.
<a href="https://www.are.na/marshall-bowers/best-albums-of-2015">Here</a> you will find my top 15 albums of 2015, as reported by <a href="http://www.last.fm/user/maxdeviant/library/albums?date_preset=LAST_365_DAYS">last.fm</a>.</p>
<h2 id="reflection">Reflection</h2>
<p>Reading <a href="https://maxdeviant.com/posts/2014/un-nouveau-soleil/">last year's end-of-year post</a>, I get a feeling of déjà vu. Since I do not think I can say it better, I will just stick to quoting myself:</p>
<blockquote>
<p>Each new year is a chance at a fresh start. A time to burn bridges and kick bad habits. A time to cut out the cancer from one's life and hone in on the desired qualities. A time for new routines, new mentors, and new ways of thinking.</p>
</blockquote>
<p>These words, written 365 days ago, were as pertinent then as they are today. It goes without saying that there are many things which will not be joining me in 2016.</p>
<h2 id="resolutions">Resolutions</h2>
<p>I hate writing "New Year's resolutions" in the trite sense of the phrase. Self-improvement is a continual process, not just something that the masses do on December 31st. That being said, there is something wonderful about looking at a blank slate and visualising all of the things that can be done with it.</p>
<p>In no particular order, here are some things I will strive to change in 2016:</p>
<ul>
<li>Spend more meaningful time with my friends</li>
<li>Develop more personal friendships with those I admire</li>
<li>Start weightlifting and tone my physique</li>
<li>Buy a bike and start riding</li>
<li>Ship one of my (numerous) personal projects</li>
<li>Automate more (especially at work)</li>
<li>Drink less alcohol</li>
<li>Read 12 books</li>
<li>Write 24 blog posts</li>
<li>Start using a solid backup solution</li>
<li>Push 10k Git commits</li>
<li>Get my first big tattoo</li>
<li>Make it to Platinum in League of Legends</li>
<li>Leave the country (for a little while)</li>
<li>Minimize my possessions</li>
<li>Graduate college with honors</li>
<li>Relearn and practice my Mandarin</li>
</ul>
<h2 id="shoutouts">Shoutouts</h2>
<p>There are lots of people who inspire me every day, and I think it is only fitting that I thank them appropriately for their positive impacts on my life. I apologize in advance for any embarrassment I may induce with these public acknowledgements.</p>
<h3 id="mom">Mom</h3>
<p>I know I definitely do not tell my mother that I love her as much as I should. But I really do love her. I admire her for her constant compassion and love for those around her. I appreciate her for putting up with all of my issues and quirks, especially my recent propensity for drinking. Life is not easy, but my mom makes it endurable.</p>
<h3 id="dad">Dad</h3>
<p>I probably do not tell me father that I love him enough either. He is the hardest working man that I know, often for little to no reward. This past year has been financially difficult for our family, but I have never seen him falter in the face of it. I admire and respect him more than anyone else in the world, and I would be lucky to be like him some day.</p>
<h3 id="calli">Calli</h3>
<p>I have known Calli since our freshman year of college, and despite me being rather distant at times, I have the privilege of calling her my best friend. I am thankful that she has been there for me, especially during the past few difficult months, and has kept me grounded through the good and the bad. I do not know what this year holds for us, especially with college graduation rapidly approaching, but I know that we will always be there for each other.</p>
<h3 id="neauoire">Neauoire</h3>
<p><a href="https://twitter.com/neauoire">Neauoire</a> aka <a href="https://aliceffekt.bandcamp.com/">Aliceffekt</a> aka Devine Lu Linvega has been a constant inspiration to me since I first discovered and met him in October 2014. His artfulness in everything he puts his hand to is incredible. But I think that even more inspiring than his works themselves is the process that surrounds them. I do not know Devine nearly as well as I would like, but the extent to which he has impacted me is phenomenal. As he pursues his dreams of <a href="https://www.patreon.com/100">living on a boat</a> with <a href="https://twitter.com/RekkaBell">Rekka</a>, I wish him the best of fortunes in his adventures on the high seas.</p>
<h3 id="anand">Anand</h3>
<p><a href="https://twitter.com/cmpstface">Anand</a> is the only person I consider a close friend who I have yet to meet in real life. He is also the only person so far who has been able to guess my MBTI type correctly on his first try. He brings such a unique perspective on the world whenever I talk with him, and there have been many late nights where we have done just that. Anand is also a great artist, and crafts experiences that are almost as remarkable as he is.</p>
<h3 id="rich">Rich</h3>
<p>Rich is my boss at Gravic. I have worked with him a lot over the past year, and have spent countless hours in his office brainstorming and bouncing ideas off of him. He is a great programmer, and even though we disagree sometimes about things (like comment syntax), I value and respect his opinion. Rich always offers a fresh perspective on a problem, and often suggests something that I would not have considered initially. I look forward to working with him more in the coming year.</p>
<h3 id="chris">Chris</h3>
<p>Chris is my <em>other</em> boss at Gravic and Rich's boss. Much of my interaction with Chris is having my bad ideas shot down by him. And while I found this incredibly frustrating at first, I have grown to appreciate his rationale, especially when I let my ideas run away from me. Much like with Rich, I value Chris' input highly, and will often run an idea by him just to make sure that I have not let some major piece slip through the cracks. He has helped mold me into a much better programmer than what I was a year before.</p>
<h3 id="et-al">et al.</h3>
<p>There are far too many people to thank for being in my life in 2015. If you were not mentioned, know that I am still thankful for you, and chances are I will make that known to you in a less public fashion.</p>
<h2 id="outro">Outro</h2>
<p>I have big expectations for 2016, perhaps too big. I do not think there is anything intrinsically special about 2016, in particular. I just know that I have been given 31,622,400 more seconds to live my life in the best way I know how. And when those seconds are over, I will look back and see how I did.</p>
Un Nouveau Soleil2014-12-31T05:00:00+00:002014-12-31T05:00:00+00:00Unknownhttps://maxdeviant.com/posts/2014/un-nouveau-soleil/<p>With 2014 rapidly coming to a close, I find myself in the company of countless others who are, no doubt, also reflecting on the past 365.25 days. Each new year seems like an eternity, yet at the end of each one the time feels like it has evaporated into thin air.</p>
<p>2014, much like any other year, has come with its ups and downs. Measuring the objective "goodness" of a year is a daunting task, as there are entirely too many contributing factors to take into account. But as a whole, I think I would call this year a good one indeed. In particular, this year has been marked by great strides in self-growth.</p>
<p>This year, I have:</p>
<ul>
<li>Pushed 1930 commits to GitHub</li>
<li>Achieved my highest semester GPA thus far: 3.73</li>
<li>Developed a love of functional programming through Lisp, ML, and Haskell</li>
<li>Gained experience setting up and administrating production servers</li>
</ul>
<p>I also listened to a lot of music in 2014. <a href="https://www.are.na/marshall-bowers/best-albums-of-2014">Here</a> are my top 14 albums of '14 by play count.</p>
<p>I have spent the past few weeks of my professional life migrating pages upon pages of archived website material to the new company website. Over the course of this mundane activity, I have been dwelling on my propensity towards leaving things in the past. While I may not have much say in whether or not the 2006 company newsletter is carried over to the new website, I do have such control over matters in my own life. That being said, there is plenty of baggage that I intend to leave behind in the current year.</p>
<p>Each new year is a chance at a fresh start. A time to burn bridges and kick bad habits. A time to cut out the cancer from one's life and hone in on the desired qualities. A time for new routines, new mentors, and new ways of thinking.</p>
<p>Despite my commitment to perpetually improving myself, I have never been one for making New Year's resolutions. My only resolution is that one year from now, I will be a vast improvement over my current self. As of right now, I do not know what that means, just as I do not know what the next year holds in store. In the coming 8800 hours, I will explore and define what that vast self-improvement may be.</p>
<p>The world is a vast place, and even though I have been to both sides of the globe and back, there is still so much that I have yet to see and do. To be content with where I am is a waste of the possibilities that lie within my reach.</p>
<p>This coming year, I want to do more with what I have been given. I want to be more than I previously have. I want to experience life to the fullest extent possible.</p>
<p>Here's to 2015: Un nouveau soleil.</p>