nathan griffithZola2022-05-17T00:00:00+00:00https://ngriffith.com/atom.xmldemo_mode2022-05-17T00:00:00+00:002022-05-17T00:00:00+00:00https://ngriffith.com/projects/demo-mode/RAILS_ENV=demo (RailsConf '22)2022-05-17T00:00:00+00:002022-05-17T00:00:00+00:00https://ngriffith.com/talks/railsconf-2022/<p>This year I had the opportunity to talk at RailsConf 2022 in
Portland, OR! The talk was titled <strong>RAILS_ENV=demo</strong>, and was
about my team's multi-year journey to shipping a reliable demo
environment, as well as the friends (or, rather, "personas") we
made along the way!</p>
<p>It's now <a href="https://www.youtube.com/watch?v=VibJu9IMohc">live on YouTube</a>:</p>
<div class="embed">
<iframe
src="https://www.youtube.com/embed/VibJu9IMohc"
webkitallowfullscreen
mozallowfullscreen
allowfullscreen>
</iframe>
</div>
<p>I decided to try something new, and constructed the slides with
<a href="https://sli.dev/"><strong>Slidev</strong></a> and <a href="https://excalidraw.com/"><strong>Excalidraw</strong></a>.
Click here to view them online:</p>
<p><a href="https://railsconf-2022.ngriffith.com"><img src="https://ngriffith.com/talks/railsconf-2022/rails-env-equals-demo.png" alt="RAILS_ENV=demo cover slide" /></a></p>
<p>A PDF is also available
<a href="https://railsconf-2022.ngriffith.com/RAILS_ENV-demo--NathanGriffith--RailsConf2022.pdf"><strong>here</strong></a>,
and the source code available <a href="https://github.com/smudge/decks/tree/master/railsconf-2022">on
GitHub</a>.</p>
<p>And here's the abstract from the RailsConf program/website:</p>
<blockquote>
<p>Today’s the day. You’ve prepared your pitch, deployed a special copy of your
app, and confirmed—in a trial run—that your walkthrough is ready for a live
audience. But, now, when you attempt to log in, something breaks. Flustered,
you debug, apologize, and debug some more, before finally calling it quits.
Next time, you’ll bring a prerecorded screencast... 😮💨</p>
<p>What could’ve been done to make the app more reliably "demoable"? Join us, as
we use "stateful fakes" and "personas" to produce a testable, maintainable,
and failure-resistant "demo" deployment, with production-like uptime
guarantees!</p>
</blockquote>
RubyConf 20212021-11-16T00:00:00+00:002021-11-16T00:00:00+00:00https://ngriffith.com/writing/rubyconf-2021/<p>Last week, despite being a Rubyist for 14 years, I attended my very first
RubyConf!</p>
<div class="image"><img src="banner.png" style="width:80%;max-width:400px" /></div>
<p>The trip to Denver was also a great opportunity to see my grandparents for the
first time since the pandemic, and it also marked my return to in-person
conferences.</p>
<p>I took a bunch of <a href="https://ngriffith.com/writing/rubyconf-2021/rubyconf2021-notes.pdf">notes</a> and produced a giant Tweet
thread about it that I'll attempt to unroll below (you may need to wait for
twitter to load them all):</p>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">Now that <a href="https://twitter.com/hashtag/RubyConf2021?src=hash&ref_src=twsrc%5Etfw">#RubyConf2021</a> is over and I've had a chance to collect my thoughts, I wanted to shout out to all of the talks I attended last week! *Every single one* was excellent!<br><br>Note: this is just a list of talks that I *happened* to see! (I wish I could've watched them all!)<br><br>🧵👇 <a href="https://t.co/28vJheeu9X">pic.twitter.com/28vJheeu9X</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815049350516737?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">Also, I'll grow this thread as I watch more virtual talks, so if you were at <a href="https://twitter.com/hashtag/RubyConf2021?src=hash&ref_src=twsrc%5Etfw">#RubyConf2021</a> and have any recs, DM me!<br><br>Lastly, these talks are not public yet, but once they are I'll post the YouTube links on my website (<a href="https://t.co/XPEmsvWavt">https://t.co/XPEmsvWavt</a>).<br><br>💎 Without further ado:</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815053637173253?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Monday was a slower day, but I caught <a href="https://twitter.com/peterzhu2118?ref_src=twsrc%5Etfw">@peterzhu2118</a> and <a href="https://twitter.com/eightbitraptor?ref_src=twsrc%5Etfw">@eightbitraptor</a>'s overview of their Variable Width Allocation project. If you've ever wondered why Ruby strings < 24 chars are somehow faster, check this out. Excited to see their work optimizing memory allocation perf!</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815056376147970?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 I also caught a combined live session of <a href="https://twitter.com/remote_ruby?ref_src=twsrc%5Etfw">@remote_ruby</a>, <a href="https://twitter.com/rorpodcast?ref_src=twsrc%5Etfw">@rorpodcast</a>, and <a href="https://twitter.com/JasonSwett?ref_src=twsrc%5Etfw">@JasonSwett</a>'s "Code With Jason". A fun meta-podcast about podcasting, and some good shout-outs to up-and-coming Rubyists & Ruby heroes.<br><br>It was also just really nice to finally put some faces to voices!</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815058921996289?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Then on Tuesday we had <a href="https://twitter.com/yukihiro_matz?ref_src=twsrc%5Etfw">@yukihiro_matz</a>'s opening keynote. I love all of the new improvements in Ruby 3.0, and look forward to "Ruby 3x3 Redux" 😀! Even after seeing his dreams for Ruby come true, Matz says "I am *still* a dreamer" ❤️❤️❤️<br><br>Thank you Matz, and keep dreaming!!!!</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815061333778436?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 It's always a pleasure to watch <a href="https://twitter.com/searls?ref_src=twsrc%5Etfw">@searls</a> speak, and his "how to make a gem of a gem" session was superb and eminently shareable. I relate so much to being "care mad" -- to find the right motivation, you sometimes have to "surround yourself with mild irritants." <a href="https://t.co/8iQlPAF04p">pic.twitter.com/8iQlPAF04p</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815067495251977?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Next up was <a href="https://twitter.com/fractaledmind?ref_src=twsrc%5Etfw">@fractaledmind</a>'s dive into `acidic_job`, aiming to give `sidekiq` the ACID-guarantees of a database-backed queue. This was a fantastic demonstration of the "job drain"/"outbox" pattern in action, and I hope that it influences the future direction of `sidekiq`!</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815070930345986?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">❗But SIDEBAR: The topic of job queue resiliency is important to me, and I was both surprised and deeply honored to see <a href="https://twitter.com/fractaledmind?ref_src=twsrc%5Etfw">@fractaledmind</a> reference my <a href="https://twitter.com/hashtag/RailsConf2021?src=hash&ref_src=twsrc%5Etfw">#RailsConf2021</a> talk from April. Thank you Stephen! It was great to meet you, and I hope I didn't seem too flustered afterwards! <a href="https://t.co/m7nCut6MM6">pic.twitter.com/m7nCut6MM6</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815076542365699?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Next up was <a href="https://twitter.com/flavorjones?ref_src=twsrc%5Etfw">@flavorjones</a>'s talk on shipping native extensions, specifically `nokogiri`'s new precompiled binaries. This is SUCH impactful--and, I imagine, relatively thankless--work. So *thank you*, Mike!<br><br>Also: YES to the call for MFA on all <a href="https://t.co/q5S3Gv6Km5">https://t.co/q5S3Gv6Km5</a> accounts.</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815079377707010?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 During the pandemic, I got really into Admiral Cloudberg's analyses of historical aviation disasters, and so I *could not miss* <a href="https://twitter.com/nmeans?ref_src=twsrc%5Etfw">@nmeans</a>'s systems-focused talk on the feedback loops that led to 2 tragic Boeing 737 Max crashes and the surrounding crisis. Deeply compelling stuff!</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815081680347137?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Then came <a href="https://twitter.com/betsythemuffin?ref_src=twsrc%5Etfw">@betsythemuffin</a>'s "Your Team, As Saga," on how so much of our behaviors and choices are informed by the stories we tell ourselves (and who we feel the "main character" even is). I want to think more about making a positive impact by managing the collective Narrative.</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815083647471616?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 One of my most anticipated talks (and not just because he's a fellow <a href="https://twitter.com/Betterment?ref_src=twsrc%5Etfw">@Betterment</a>-er) was <a href="https://twitter.com/ancat?ref_src=twsrc%5Etfw">@ancat</a>'s, giving us a view into the mind of a security engineer, in which he meets Rubyists where they are, with security-minded linter rules that maximize learning and minimize toil. <a href="https://t.co/iWFxL16eF4">pic.twitter.com/iWFxL16eF4</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815091734126595?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">❗Side bar (again): I worked with <a href="https://twitter.com/ancat?ref_src=twsrc%5Etfw">@ancat</a> on getting some of his linter rules shipped to production, and I have to say, they are *brilliant*. These are some easy-to-use-yet-extremely-thorough checks against common authorization issues. Check them out here:<a href="https://t.co/RofWBaUxMD">https://t.co/RofWBaUxMD</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815095718678534?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 We closed Tuesday with <a href="https://twitter.com/FutureofWomen?ref_src=twsrc%5Etfw">@FutureofWomen</a>'s keynote: "Finding Purpose and Cultivating Spirituality." Connecting more deeply with oneself (via mindfulness/affirmations) is so important for people who would otherwise let fear and avoidance drive their decision-making (so, um, *me*).</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815099120308224?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 And Wednesday started off with <a href="https://twitter.com/testobsessed?ref_src=twsrc%5Etfw">@testobsessed</a>'s talk on feedback cycles. I loved the whole thing (really, just every new topic and idea), but I especially loved the segments on the Theory of Agile vs the Reality of Agile, as well as this hilarious git branching diagram: <a href="https://t.co/Hzd4AjToX8">pic.twitter.com/Hzd4AjToX8</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815104224772096?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Next, I attended <a href="https://twitter.com/Jake_Evelyn?ref_src=twsrc%5Etfw">@Jake_Evelyn</a> and <a href="https://twitter.com/JemmaIssroff?ref_src=twsrc%5Etfw">@JemmaIssroff</a>'s insightful (and extremely well-explained) dive into optimizing the performance of their metaprogramming-backed gem, `memo_wise`. It made me want to dive in and start profiling/benchmarking some of my own Ruby DSLs!</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815107215216640?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Then, <a href="https://twitter.com/jenny_codes?ref_src=twsrc%5Etfw">@jenny_codes</a>'s talked about how to abuse (and, really, how to avoid the need to abuse) test doubles. I loved the Douglas Adams quote, the "testing pyramid, reality edition," and the super clear code examples. Separating *decisions* from *delegations* was a great insight! <a href="https://t.co/imPqvfxXVd">pic.twitter.com/imPqvfxXVd</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815112646897671?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Here's something I CANNOT WAIT to try out: *compiling* Ruby w/ Sorbet + LLVM. <a href="https://twitter.com/jez_io?ref_src=twsrc%5Etfw">@jez_io</a> and <a href="https://twitter.com/moltarx?ref_src=twsrc%5Etfw">@moltarx</a>'s talk was not only a great summary of the benefits of ahead-of-time compilation, it also presented a great example of how to do incremental rollouts (and how to do them well).</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815116040032260?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 If you liked The Good Place, you'll enjoy <a href="https://twitter.com/kdreyeroren?ref_src=twsrc%5Etfw">@kdreyeroren</a>'s talk on Contractualism, presenting a great model of the ethics & impact of our work on our users, on *their* users, and on the wider world. In my humble opinion, this talk should be given at *every single tech company*.</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815118296653826?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">❗Side bar: I loved the track curation this year. Both the "Ethics" and "Complex Sociotechnical Systems" tracks were full of compelling talks, and their presence at all really speaks to the thoughtful leadership of the <a href="https://twitter.com/rubyconf?ref_src=twsrc%5Etfw">@RubyConf</a> organizers and track directors. 👏👏👏</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815120712482816?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 I always love <a href="https://twitter.com/eileencodes?ref_src=twsrc%5Etfw">@eileencodes</a>'s deep-yet-accessible dives into Ruby/Rails internals, and her talk on speeding up class variables was no exception. Take-away: OSS contributions are a *negotiation*, and it's important that we all do it. I'm definitely gonna try to be a "C tourist."</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815123191406599?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 <a href="https://twitter.com/jmeller?ref_src=twsrc%5Etfw">@jmeller</a>'s talk on (dis)honesty in our industry is another must-see. If you take anything away, you *must* ask yourself whether your software solution would have the same value prop if it prompted your users for *informed consent*. "Honest software is a competitive advantage."</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815125418586115?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">And that leads us to the end of the conference.<br><br>💎 But first, a quick shout out to <a href="https://twitter.com/ViraniRiaz?ref_src=twsrc%5Etfw">@ViraniRiaz</a>'s pre-keynote "whimsy" session on all of the hilariously absurd things you can do with `pry`, including editing classes in real-time. <a href="https://twitter.com/rowangoldenarch?ref_src=twsrc%5Etfw">@rowangoldenarch</a>, you'll love this one!</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815127687704580?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">💎 Finally, we closed with Matz's Q&A, led by <a href="https://twitter.com/evanphx?ref_src=twsrc%5Etfw">@evanphx</a>. I'm looking forward to the new tools-focused core Ruby development. Plus, it was fun to hear Matz's thoughts on Rust & Zig (& TypeScript & Python), and what we can *learn* from the hard work that goes into these ecosystems.</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815130061688834?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">👋 One last note. When I looked around at a spaced-apart hall full of masked faces, all watching a remotely-delivered (and pre-recorded) closing video, I had a surreal moment of clarity. I realized that, 2 years ago, such a scene would've seemed... well, terrifying. Unreal. (1/3) <a href="https://t.co/U10K1TqZw6">pic.twitter.com/U10K1TqZw6</a></p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460815363806015494?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">Yet, last week, being in a room like that felt... hopeful.<br><br>The last couple years have been hard (full stop), and each of us has had to experience this pandemic in our own way. But, for three whole days, we were able to come together and create experiences together. (2/3)</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460816262553030659?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<blockquote class="twitter-tweet" data-conversation="none" data-dnt="true"><p lang="en" dir="ltr">And it wouldn't have been possible if not for the staff and organizers who worked to get us all (both remote and in person) there. So, my deepest thanks goes out to the entire <a href="https://twitter.com/rubyconf?ref_src=twsrc%5Etfw">@RubyConf</a> team for doing, like, two+ conferences-worth of work to make all of this happen!! 👏👏🤗 (3/3)</p>— 🚱 Nathan Griffith (@smudgethefirst) <a href="https://twitter.com/smudgethefirst/status/1460816500802170882?ref_src=twsrc%5Etfw">November 17, 2021</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
delayed2021-08-17T00:00:00+00:002021-08-17T00:00:00+00:00https://ngriffith.com/projects/delayed/imagesnap2021-05-12T00:00:00+00:002021-05-12T00:00:00+00:00https://ngriffith.com/projects/imagesnap/Can I Break This? (RailsConf '21)2021-04-12T00:00:00+00:002021-04-12T00:00:00+00:00https://ngriffith.com/talks/railsconf-2021/<p>This year I had the opportunity to present a talk at RailsConf 2021!</p>
<div class="image"><img src="banner.jpg" style="width:80%;max-width:400px" /></div>
<p>The conference was 100% remote, so I recorded everything in advance, and then had a live Q&A session over discord.
As of May 2021, the conference-branded version of the talk is now available on YouTube:</p>
<div class="embed">
<iframe
src="https://www.youtube.com/embed/TuhS13rBoVY"
webkitallowfullscreen
mozallowfullscreen
allowfullscreen>
</iframe>
</div>
<p>Click here to download my slides (with code errors corrected):</p>
<div class="image">
<a href="can-i-break-this-railsconf-2021.pdf" target="_blank">
<image src="can-i-break-this.jpg" alt="Can I Break This?" style='max-width:300px;width:100%;' />
</a>
</div>
<h3 id="errata">Errata:</h3>
<ul>
<li>
<p>At <strong>20:00</strong> I demonstrate the use of <code>lock!</code> on two account records. A couple of conference-goers rightly pointed out that such a strategy could be subject to a <em>deadlock,</em> a topic that I cut for time. In practice, deadlocks are one of many low-probability reasons a method might fail, so thankfully the resiliency strategies I cover should help mitigate them.</p>
<p>I'd also add that it's not actually the explicit use of <code>lock!</code> so much as the <code>transaction</code> that is the source of the deadlock. Most SQL databases will implicitly lock rows on <code>UPDATE</code> or <code>DELETE</code>, and by eagerly locking both records up-front, we may actually be reducing the overall chance of a deadlock occurring.</p>
</li>
<li>
<p>At <strong>24:30</strong>, the slide should read <code>deliver_later</code> instead of <code>deliver_now</code>.</p>
</li>
<li>
<p>At <strong>27:39</strong>, the slide should read <code>deliver_later</code> instead of <code>deliver_now</code>.</p>
</li>
<li>
<p>At <strong>35:01</strong> I state that most job backends guarantee "at-least-once execution," but on second thought, I'm not actually sure this is true. My understanding is that Redis-backed queues tend to use a delete-on-pickup strategy, which means that if the server process crashes or is halted, these jobs may never have a chance to complete.</p>
<p>The pro version of <code>sidekiq</code> claims to optionally support at-least-once durability, by relying on Redis' atomic <code>RPOPLPUSH</code> to move jobs to an in-progress queue, deleting them only after they complete. (Similar to the way that most DB-backed queues work by default.)</p>
<p>Of course, if you can't guarantee message delivery at the time of enqueue, then you effectively don't have at-least-once execution guarantees, regardless of your queue backend.</p>
</li>
<li>
<p>At <strong>36:57</strong>, the upper right <code>save</code> method should read something like <code>if !order.in_progress? ...</code> instead of <code>if !order.completed? ...</code> because the subsequent update sets <code>in_progress: true</code>, and we must check that state in order to avoid race conditions. (I had been planning on adding a segment covering <em>state machines,</em> but it got cut for time. If you're interested in how I track state across parts of persistence operations, check out the <a href="https://github.com/betterment/steady_state">steady_state</a> gem!)</p>
</li>
</ul>
All-In on Pattern Matching2021-01-05T00:00:00+00:002021-01-05T00:00:00+00:00https://ngriffith.com/writing/all-in-on-pattern-matching/<p>I'd like to share a snippet of code I've been honing over the past few days.</p>
<p>For context, I'm working on a command-line interface
for taking photos from a webcam. I'm aiming to make it compatible as a drop-in replacement for the mac-based
<a href="https://github.com/rharder/imagesnap">imagesnap</a> tool, and I'm writing it in Rust in the hopes of enabling
cross-platform support. But for now, I'm focusing on a mac implementation and am (with a bit of magic) interfacing
with one of macOS's Objective-C libraries (the <a href="https://developer.apple.com/av-foundation/">AVFoundation</a> framework). </p>
<p><strong>But none of that matters to this post.</strong> What matters is that I'm building a tool that will accept inputs
from humans and translate those to a series of nontrivial behaviors under the hood. For anyone familiar with this
kind of shell-command work, this means accepting and parsing a series of arguments that might be supplied in any
number of combinations:</p>
<pre style="background-color:#2b303b;">
<code class="language-bash" data-lang="bash"><span style="color:#bf616a;">$</span><span style="color:#c0c5ce;"> imagesnap</span><span style="color:#bf616a;"> -q -w</span><span style="color:#c0c5ce;"> 2</span><span style="color:#bf616a;"> -d </span><span style="color:#c0c5ce;">"</span><span style="color:#a3be8c;">Microsoft LifeCam HD-3000</span><span style="color:#c0c5ce;">" 2020-01-05-test-snapshot-1.jpg
</span><span style="color:#bf616a;">Capturing</span><span style="color:#c0c5ce;"> image from device "</span><span style="color:#a3be8c;">Microsoft LifeCam HD-3000</span><span style="color:#c0c5ce;">"..................2020-01-05-test-snapshot-1.jpg
</span></code></pre>
<p>This also means accepting zero arguments:</p>
<pre style="background-color:#2b303b;">
<code class="language-bash" data-lang="bash"><span style="color:#bf616a;">$</span><span style="color:#c0c5ce;"> imagesnap
</span><span style="color:#bf616a;">Capturing</span><span style="color:#c0c5ce;"> image from device "</span><span style="color:#a3be8c;">FaceTime HD Camera (Built-in)</span><span style="color:#c0c5ce;">"..................snapshot.jpg
</span></code></pre>
<p>And, of course, it means gracefully handling invalid inputs as well:</p>
<pre style="background-color:#2b303b;">
<code class="language-bash" data-lang="bash"><span style="color:#bf616a;">$</span><span style="color:#c0c5ce;"> imagesnap</span><span style="color:#bf616a;"> -xzfv
Error:</span><span style="color:#c0c5ce;"> Unrecognized option: '</span><span style="color:#a3be8c;">x</span><span style="color:#c0c5ce;">'
</span></code></pre>
<p>The fact that this kind of human-machine interface pattern allows for practically limitless input permutations is
what makes it so powerful and enduring. But it also presents an age-old challenge: we must design a <code>main</code> method
that can make sense of these inputs and conditionally adjust its behavior.</p>
<p>And if there's one thing that I can say about conditionals, it's that the more of them you have, the more subtle
the bugs are, and the harder it becomes to reason about edge cases.</p>
<p>Let's take a look at what this commonly entails.</p>
<h3 id="conditional-argument-handling-in-practice">Conditional Argument-Handling In Practice</h3>
<p>In Rust, as in most languages, getting the list of user-supplied inputs is super easy:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> args: Vec<String> = std::env::args().</span><span style="color:#96b5b4;">collect</span><span style="color:#c0c5ce;">();
</span></code></pre>
<p>For the sake of conciseness, we can then use the <code>getopts</code> library to define our program's options (and ensure that
any unrecognized arguments result in an error state):</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> opts = getopts::Options::new();
opts.</span><span style="color:#96b5b4;">optopt</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">w</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">warmup</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">Warm up camera for x seconds [0-10]</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">x.x</span><span style="color:#c0c5ce;">");
opts.</span><span style="color:#96b5b4;">optflag</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">l</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">list</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">List available capture devices</span><span style="color:#c0c5ce;">");
opts.</span><span style="color:#96b5b4;">optopt</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">d</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">device</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">Use specific capture device</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">NAME</span><span style="color:#c0c5ce;">");
opts.</span><span style="color:#96b5b4;">optflag</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">h</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">help</span><span style="color:#c0c5ce;">", "</span><span style="color:#a3be8c;">This help message</span><span style="color:#c0c5ce;">");
</span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> inputs = opts.</span><span style="color:#96b5b4;">parse</span><span style="color:#c0c5ce;">(&args[</span><span style="color:#d08770;">1</span><span style="color:#c0c5ce;">..]).</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">(); </span><span style="color:#65737e;">// might panic!
</span></code></pre>
<p>This allows us to quickly iterate on the argument-handling logic. If you're like me, your brain will naturally want
to start introducing a series of procedural steps that walk through the inputs:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> help = inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">h</span><span style="color:#c0c5ce;">");
</span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> list_devices = inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">l</span><span style="color:#c0c5ce;">");
</span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> filename = </span><span style="color:#b48ead;">if </span><span style="color:#c0c5ce;">!inputs.free.is_empty? {
inputs.free[</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">].</span><span style="color:#96b5b4;">clone</span><span style="color:#c0c5ce;">()
} </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
"</span><span style="color:#a3be8c;">snapshot.jpg</span><span style="color:#c0c5ce;">".</span><span style="color:#96b5b4;">to_string</span><span style="color:#c0c5ce;">()
}
</span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> warmup_secs = </span><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">w</span><span style="color:#c0c5ce;">") {
inputs.</span><span style="color:#96b5b4;">opt_str</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">w</span><span style="color:#c0c5ce;">").</span><span style="color:#96b5b4;">parse</span><span style="color:#c0c5ce;">().</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">()
} </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
</span><span style="color:#d08770;">0.5 </span><span style="color:#65737e;">// default based on rough manual testing
</span><span style="color:#c0c5ce;">}
</span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> device = </span><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">d</span><span style="color:#c0c5ce;">") {
inputs.</span><span style="color:#96b5b4;">opt_str</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">d</span><span style="color:#c0c5ce;">")
} </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
</span><span style="color:#96b5b4;">get_default_device</span><span style="color:#c0c5ce;">()
}
</span></code></pre>
<p>This is a perfectly sensible approach and will look very similar across a lot of C-style languages, regardless of
our use of <code>getopts</code> (which simply helps us avoid explicitly looping through every input).</p>
<p>You can start to picture where this might go—a bunch of variable assignments via a series of <code>if</code>/<code>else</code>
conditionals, all culminating in a final series of possible behaviors:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> help {
</span><span style="color:#96b5b4;">print_usage</span><span style="color:#c0c5ce;">();
} </span><span style="color:#b48ead;">else if</span><span style="color:#c0c5ce;"> list_devices {
</span><span style="color:#96b5b4;">list_devices</span><span style="color:#c0c5ce;">();
} </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
</span><span style="color:#96b5b4;">snap_image</span><span style="color:#c0c5ce;">(filename, device, warmup_secs);
}
</span></code></pre>
<p>Following so far? Good.</p>
<p>As of, say, Saturday, this is what my code looked like (more or less) and it worked well for prototyping. Of course,
I could clean it up by handling return types (instead of blindly calling <code>unwrap()</code> and hoping the runtime doesn't
panic), but for simplicity's sake, I've excluded all of the error-handling logic from the above examples. </p>
<p>And sure, there are bunch of ways I could've implemented this, but you'll probably agree that most of the above is
relatively unobjectionable and, in all likelihood, uninteresting. So let's move onto the more interesting parts.</p>
<h3 id="where-it-breaks-down">Where It Breaks Down</h3>
<p>What the above approach doesn't help me with at all are the many corner cases that hide among the conditionals.</p>
<p>The <code>getopts</code> library will already return an <code>Err</code> variant if any unrecognized arguments are passed,
but say, for example, that I'd like to react to incompatible or nonsensical <em>combinations</em> of otherwise <em>valid</em>
arguments:</p>
<pre style="background-color:#2b303b;">
<code class="language-bash" data-lang="bash"><span style="color:#bf616a;">$</span><span style="color:#c0c5ce;"> imagesnap</span><span style="color:#bf616a;"> -l -w</span><span style="color:#c0c5ce;"> 1
</span><span style="color:#bf616a;">Error:</span><span style="color:#c0c5ce;"> Invalid combination of arguments supplied!
</span></code></pre>
<p>To make that work as a conditional, I'd need to first check if both were supplied:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">l</span><span style="color:#c0c5ce;">") && inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">w</span><span style="color:#c0c5ce;">") {
eprintln!("</span><span style="color:#a3be8c;">Error: Invalid combination of arguments supplied!</span><span style="color:#c0c5ce;">");
</span><span style="color:#b48ead;">return</span><span style="color:#c0c5ce;">;
}
</span></code></pre>
<p>But doing this across all of the inputs would be quite cumbersome! I might start chaining <code>opt_present</code> calls
together with <code>&&</code>/<code>||</code> operators, or break out a series of conditionals to check for interesting permutations, but
this expansion of conditional behavior is prone to error, and commonly results in <code>main</code> methods becoming littered
with guard clauses (often in response to user-reported bugs).</p>
<p>Alternatively, I could choose to focus on stricter conditions around each behavior:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">l</span><span style="color:#c0c5ce;">") {
</span><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> no_incompatible_options_present {
</span><span style="color:#96b5b4;">list_devices</span><span style="color:#c0c5ce;">();
} </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
eprintln!("</span><span style="color:#a3be8c;">Error: Invalid combination of arguments supplied!</span><span style="color:#c0c5ce;">");
}
}
</span></code></pre>
<p>Yet, once again, solving for the "no incompatible options" condition will be annoying to reason about,
and may require an update every time I add a new program argument.</p>
<p>A common resolution to the above conundrum is, simply, to not solve it, and rely on the user to supply valid inputs,
lest they encounter undefined behavior. Such tendencies are a result the high cost of getting it right, relative to
the low cost of getting it wrong. <em>It's a minor irk, easily worked around, so why bother?</em> I wouldn't fault anyone
for making such a trade-off.</p>
<p>But what if we could make it easier? What if there were a low-cost way of <em>visualizing</em> the whole matrix of possible
inputs, and expressing that visualization in code?</p>
<h3 id="patterns-and-matching">Patterns and Matching</h3>
<p>Yep, this is where pattern matching comes in.</p>
<p>As a quick example of how Rust's match syntax works, let's look at that filename conditional from before:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> filename = </span><span style="color:#b48ead;">if </span><span style="color:#c0c5ce;">!inputs.free.is_empty? {
inputs.free[</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">].</span><span style="color:#96b5b4;">clone</span><span style="color:#c0c5ce;">()
} </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
"</span><span style="color:#a3be8c;">snapshot.jpg</span><span style="color:#c0c5ce;">".</span><span style="color:#96b5b4;">to_string</span><span style="color:#c0c5ce;">()
}
</span></code></pre>
<p>This could instead be expressed as a pattern match:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> filename = </span><span style="color:#b48ead;">match</span><span style="color:#c0c5ce;"> inputs.free.</span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">) {
Some(name) => name.</span><span style="color:#96b5b4;">clone</span><span style="color:#c0c5ce;">(),
None => "</span><span style="color:#a3be8c;">snapshot.jpg</span><span style="color:#c0c5ce;">".</span><span style="color:#96b5b4;">to_string</span><span style="color:#c0c5ce;">(),
}
</span></code></pre>
<p>Fans of functional languages will recognize this pattern. (My first experience with pattern matching was with Haskell.)
But in this example, we're matching on just a single value, and even with the fancy <code>get(0)</code> call (returning an <code>Option</code>
type), the overall approach is functionally equivalent to the <code>if</code>/<code>else</code> statement.</p>
<p>Where pattern matching truly shines is when we can compose multiple values and types. Case in point:
that "no incompatible options" problem from before. What if we solved that by matching for
patterns of <em>multiple inputs</em>?</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">match </span><span style="color:#c0c5ce;">(
inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">l</span><span style="color:#c0c5ce;">"),
inputs.</span><span style="color:#96b5b4;">opt_str</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">w</span><span style="color:#c0c5ce;">"),
inputs.</span><span style="color:#96b5b4;">opt_str</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">d</span><span style="color:#c0c5ce;">"),
) {
(</span><span style="color:#d08770;">true</span><span style="color:#c0c5ce;">, None, None) => </span><span style="color:#96b5b4;">list_devices</span><span style="color:#c0c5ce;">(),
(</span><span style="color:#d08770;">true</span><span style="color:#c0c5ce;">, _, _) => eprintln!("</span><span style="color:#a3be8c;">--list/-l is incompatible with other args!</span><span style="color:#c0c5ce;">"),
(_, _, _) => </span><span style="color:#96b5b4;">continue_execution</span><span style="color:#c0c5ce;">(),
}
</span></code></pre>
<p>Now we're talking! Similar to before, we're relying on <code>opt_str</code> to return an <code>Option</code> type. In other languages,
this might be a null-check, but in Rust, we can pattern-match on type variants and guarantee that we've covered all
possible permutations of these three inputs.</p>
<p><strong>Yes, that's a guarantee.</strong> Rust won't compile our code unless we've accounted for all possible <code>match</code> branches.
This encourages us to rely on wildcard/catch-all arms like that <code>continue_execution()</code> arm above.</p>
<h3 id="going-all-in">Going All-In</h3>
<p>So why not go all the way, and include <em>all</em> of our inputs in a single <code>match</code>? Some might call it crazy, but I've
found incredible utility in pattern-matching on inputs, early and often.</p>
<p>Sure, it won't prevent logical bugs in the way we parse and handle args, but it will at least ensure that we express
<em>all possible permutations</em> of user input. This provide a much better starting place than the previous pile of
loosely-related conditionals.</p>
<p>Convinced? Or, at least intrigued? Great. Here's our initial structure, accounting for every user input:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">match </span><span style="color:#c0c5ce;">(
inputs.free.</span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">), </span><span style="color:#65737e;">// optional filename
</span><span style="color:#c0c5ce;"> inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">l</span><span style="color:#c0c5ce;">"),
inputs.</span><span style="color:#96b5b4;">opt_present</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">h</span><span style="color:#c0c5ce;">"),
inputs.</span><span style="color:#96b5b4;">opt_str</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">d</span><span style="color:#c0c5ce;">"),
inputs.</span><span style="color:#96b5b4;">opt_str</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">w</span><span style="color:#c0c5ce;">"),
) {
(file, </span><span style="color:#d08770;">false</span><span style="color:#c0c5ce;">, </span><span style="color:#d08770;">false</span><span style="color:#c0c5ce;">, device, warmup) => {
</span><span style="color:#96b5b4;">snap_image</span><span style="color:#c0c5ce;">(file, device, warmup) </span><span style="color:#65737e;">// todo: handle Option types
</span><span style="color:#c0c5ce;">}
(None, </span><span style="color:#d08770;">true</span><span style="color:#c0c5ce;">, </span><span style="color:#d08770;">false</span><span style="color:#c0c5ce;">, None, None) => </span><span style="color:#96b5b4;">list_devices</span><span style="color:#c0c5ce;">(),
(None, </span><span style="color:#d08770;">false</span><span style="color:#c0c5ce;">, </span><span style="color:#d08770;">true</span><span style="color:#c0c5ce;">, None, None) => </span><span style="color:#96b5b4;">print_usage</span><span style="color:#c0c5ce;">(),
(_, _, _, _, _) => eprintln!("</span><span style="color:#a3be8c;">Invalid combination of arguments.</span><span style="color:#c0c5ce;">"),
}
</span></code></pre>
<p>By matching on <code>None</code> in the <code>list_devices()</code> and <code>print_usage()</code> cases, we ensure that those arguments can only
be used in isolation, otherwise the match will fail and we'll fall through to the "invalid combination" arm!
Of course, in the <code>snap_image()</code> case we're missing some <code>Option</code>-handling, but let's assume that method is capable
of accepting <code>None</code> types and applying default values.</p>
<p>What we're left with is an incredibly concise representation of the way the logic flows
from inputs to actions/outcomes. Each arm summarizes a set of compatible inputs that lead to a specific behavior.
We should avoid letting two arms lead to the same behavior, and rely on our type system/variants to abstract
some of those details away.</p>
<h3 id="adding-more-arms">Adding More Arms</h3>
<p>We may be missing a few corner cases, but accounting for them is simply a matter of adding a new pattern arm!</p>
<p>One thing that immediately jumps out at me is that I've forgotten to handle extraneous arguments. If someone supplies
two or more non-option arguments instead of one, I'd like to return an error. This can be accomplished by adding an
additional match on the 2nd argument, and expecting it to be <code>None</code> in all but the catch-all case:</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">match </span><span style="color:#c0c5ce;">(
inputs.free.</span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">), </span><span style="color:#65737e;">// optional filename
</span><span style="color:#c0c5ce;"> inputs.free.</span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(</span><span style="color:#d08770;">1</span><span style="color:#c0c5ce;">), </span><span style="color:#65737e;">// 2nd free arg should always be `None`
// etc...
</span><span style="color:#c0c5ce;">) {
(file, None, ...) => </span><span style="color:#96b5b4;">snap_image</span><span style="color:#c0c5ce;">(...),
</span><span style="color:#65737e;">// etc...
</span><span style="color:#c0c5ce;">(_, _, ...) => eprintln!("</span><span style="color:#a3be8c;">Invalid combination of arguments.</span><span style="color:#c0c5ce;">"),
}
</span></code></pre>
<p>Another improvement I'd like to make is to parse and validate the "warmup" input, which must be cast to a float value.
Once again, I can slot in a couple extra match arms, and I can even add an <code>if</code> condition to the pattern match!</p>
<pre style="background-color:#2b303b;">
<code class="language-rust" data-lang="rust"><span style="color:#b48ead;">match </span><span style="color:#c0c5ce;">(
</span><span style="color:#65737e;">// Transpose lets us match on the `Result` of the parse,
// but preserve the `Option` type. `Ok(None)` is a valid input!
</span><span style="color:#c0c5ce;"> inputs.</span><span style="color:#96b5b4;">opt_str</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">w</span><span style="color:#c0c5ce;">").</span><span style="color:#96b5b4;">map</span><span style="color:#c0c5ce;">(|</span><span style="color:#bf616a;">s</span><span style="color:#c0c5ce;">| s.</span><span style="color:#96b5b4;">parse</span><span style="color:#c0c5ce;">()).</span><span style="color:#96b5b4;">transpose</span><span style="color:#c0c5ce;">(),
</span><span style="color:#65737e;">// etc...
</span><span style="color:#c0c5ce;">) {
(..., Ok(Some(warmup))) </span><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> w < </span><span style="color:#d08770;">0.0 </span><span style="color:#c0c5ce;">|| w > </span><span style="color:#d08770;">10.0 </span><span style="color:#c0c5ce;">=> {
eprintln!("</span><span style="color:#a3be8c;">Warmup must be between 0 and 10 seconds</span><span style="color:#c0c5ce;">")
}
(..., Ok(warmup)) => </span><span style="color:#96b5b4;">snap_image</span><span style="color:#c0c5ce;">(...),
(..., Err(_)) => eprintln!("</span><span style="color:#a3be8c;">Failed to parse warmup!</span><span style="color:#c0c5ce;">"),
</span><span style="color:#65737e;">// etc...
</span><span style="color:#c0c5ce;">}
</span></code></pre>
<p>Nice.</p>
<h3 id="wrapping-up">Wrapping Up</h3>
<p>The joy of pattern matching makes it easy to get a little carried-away, so of course I'll want to be mindful of
the complexity of this particular <code>match</code>. Things like numerical validations can always be tucked-away into one
of the arms. But for a simple program with only a few arguments, being able to express the full range of valid
inputs up-front is extremely satisfying, and comes with compiler-enforced guarantees!</p>
<p>And this isn't to say that the c-style <code>if</code>/<code>else</code> approach is inherently bad! I imagine that plenty of coders will
see the above <code>match</code> structure and start dry-heaving, and that's okay! Declarative programming takes some getting
used to, and not everyone will prefer its aesthetics. But for those who do, hopefully this post has sparked some
inspiration!</p>
Farewell, Middleman2020-07-09T00:00:00+00:002020-07-09T00:00:00+00:00https://ngriffith.com/writing/farewell-middleman/<p>Four years ago, I wrote <a href="http://127.0.0.1:1111/writing/farewell-jekyll/">a post</a> about porting this
website from Jekyll (the great-grandparent of all static site generators) to Middleman (a Ruby-based alternative
that takes the best features from Jekyll and bakes them into a more powerful, more flexible framework). However,
after four years of spending more time updating/maintaining the <code>Gemfile</code> than actually writing posts, I decided to
make yet another change to the way this site gets generated.</p>
<h2 id="what-i-actually-wanted">What I Actually Wanted</h2>
<p>Jekyll and Middleman already offered a ton of features that I didn't want to give up—static content servable
via HTTP, markdown support (with "frontmatter"), a lightweight templating language, etc—but I had a few items
on my wishlist when I embarked on this search.</p>
<h3 id="near-zero-boilerplate">Near-zero boilerplate</h3>
<p>I wanted to focus on maintaining <em>only</em> the text and templates necessary to render the site, and very little else.
Moving to Middleman certainly helped with this (since there are a ton of extensions available as pluginable gems),
but there were still a few large pieces of code that had to be committed to the repo—the <code>Gemfile</code> and
<code>Gemfile.lock</code>, a server config (<code>config.ru</code>), and a plugin for embedding tweets, among a few other odds & ends.</p>
<p>I had also set up a <code>Dockerfile</code> that allowed me to render the site from my Windows PC, and I wanted to do away
with this in favor of something that was easier to run natively on macOS, Windows, and Linux.</p>
<h3 id="faster-setup-times">Faster setup times</h3>
<p>Whenever I revisit this website, the first thing I do is reinstall/update its dependencies and deploy those
changes. This means that when I'm motivated to write something, I often end up staring at the <code>bundle install</code>
output, or waiting for a new version of Ruby to build (via <code>rbenv</code>). It's a small thing, but it can quickly stamp
out whatever spark of inspiration I might've had.</p>
<h3 id="built-in-shortcode-support">Built-in shortcode support</h3>
<p>One thing that had been hard to set up in Jekyll and Middleman (not impossible, just difficult) was the idea of
a "shortcode"—something that can be embedded natively in the templating language supported by the content engine.
In my case, I had been using a "tweet" shortcode to embed a tweet in <a href="/writing/hash-banging-the-intertubes/">this post</a>.</p>
<p>Rendering that in-line looked like this:</p>
<pre style="background-color:#2b303b;">
<code><span style="color:#c0c5ce;">{% tweet https://twitter.com/danwrong/status/171681426297729025 %}
</span></code></pre>
<p>However, I had been maintaining a 100+ line <code>tweet_tag.rb</code> file that was in charge of hitting the Twitter API,
caching the tweet's content, rendering a non-JS version of the tweet, and wrapping it in the JS required to fetch
and render the Twitter-enhanced version of the Tweet.</p>
<p>I wanted something that could more natively support shortcodes for any number of content types—tweets, YouTube
videos, charts & figures, GitHub gists, you name it. And if the shortcode wasn't built-in, I wanted to make sure
I could easily define a more declarative template without writing custom code, as I had done the Ruby-based solutions.</p>
<h2 id="enter-hugo-zola-and-gatsby">Enter: Hugo, Zola, and Gatsby</h2>
<p>Since I knew I wanted something that could run natively on a wide range of platforms with ease, I started first by
looking at languages <em>known</em> for solid cross-platform support. The top three that came to mind were Go and Rust, but
I also considered JS frameworks since Node's cross-platform support has gotten much better over the years.</p>
<p>This led me to look at the three top static site generators for those platforms:
<a href="https://gohugo.io/">Hugo</a>, <a href="https://www.getzola.org/">Zola</a> (formerly Gutenberg), and <a href="https://www.gatsbyjs.org/">Gatsby</a>.</p>
<h3 id="ruling-out-gatsby">Ruling out Gatsby</h3>
<p>While I was certainly impressed with Gatsby's sophisticated vision for the <a href="https://jamstack.wtf/">JAMStack</a>
architecture, I quickly realized that it violated my number one rule: reduce boilerplate to zero. You see, even
though the <a href="https://www.gatsbyjs.org/docs/gatsby-project-structure/">project structure</a> is relatively free from
excess, the majority of the site and templates must be built as React components. As such, for even simple
pages I'd end up writing and maintaining files that looked more like <em>code</em> than like <em>templates</em>.</p>
<p>If I were building an application with real interactivity (and real business value), I'd count this as a strong
positive. But since this site is mostly focused on <em>very minimal content</em>, I decided against this high-horsepower
approach in favor of something with a far more concise templating language. Especially since Gatsby's build
performance is relatively slow, as compared to its Go and Rust competitors.</p>
<h3 id="hugo-or-not-hugo-that-is-the-question">Hugo, or Not Hugo, that is the question</h3>
<p>Next up was Hugo: "the world's fastest framework for building websites"</p>
<p>Right out of the box, this Go-based product certainly ticks the "faster setup times" box. Plus, it supports shortcodes,
and since Hugo ships entirely as a binary, it runs natively on most platforms, while also cutting down on the
boilerplate.</p>
<p>With a much narrower focus on static site generation (and fewer buzzwords like "JAMStack"), Hugo appealed strongly to my
sense of finding the right tool for the job. However, there was a deal-breaker:</p>
<p><strong>I hated the <a href="https://gohugo.io/templates/introduction/">templating language</a>.</strong></p>
<p>While I'm sure it's perfectly capable, the Go-like syntax felt, at best, like overkill, and I suspected that it
would result in far more mental overhead than I was willing to take on. So I kept looking. And that's when I found...</p>
<h3 id="zola">Zola</h3>
<p>No, not the wedding planning website.</p>
<p>I mean, I <em>just</em> used that other Zola for my own wedding registry, so I wasn't particularly excited about this static
site generator's name. But looking past that, I found something that resembles Hugo (written in Rust instead of Go),
but with a much more intuitive <a href="https://tera.netlify.app/">templating language</a>.</p>
<p>From a performance standpoint, Zola's build times are
<a href="https://github.com/getzola/zola/issues/325#issuecomment-401619208">roughly equivalent</a> to Hugo's (give or take),
and everything else (boilerplate, shortcodes, cross-platform support) is functionally equivalent. To top it all off,
I've been learning Rust in my free time, so supporting something built by the Rust community also felt satisfying.</p>
<p>So, this site is now running on Zola. The fact that you wouldn't be able to tell speaks to the power of static site
generation in general -- I've been able to transition the content and layout across three different products, and
each time has been easier and has resulted in a more concise and content-focused repo. Here's the obligatory git diff:</p>
<p><img src="https://ngriffith.com/writing/farewell-middleman/middleman-zola-git-stats.png" alt="Git Stats" /></p>
<p>It's a small thing, but I find it satisfying.</p>
nightlight2020-05-22T00:00:00+00:002020-05-22T00:00:00+00:00https://ngriffith.com/projects/nightlight/brdiff2020-04-20T00:00:00+00:002020-04-20T00:00:00+00:00https://ngriffith.com/projects/brdiff/uncruft2019-04-30T00:00:00+00:002019-04-30T00:00:00+00:00https://ngriffith.com/projects/uncruft/steady_state2018-10-23T00:00:00+00:002018-10-23T00:00:00+00:00https://ngriffith.com/projects/steady-state/10,000 Days2016-06-08T00:00:00+00:002016-06-08T00:00:00+00:00https://ngriffith.com/writing/my-sweet-10000/<p>Today I turned 10,000 days old. That's a lot of days.</p>
<p>Each of those days I woke up, did something, and went to sleep (more or less). Lather, rinse,
repeat, 9,999 more times.</p>
<p>And yet, I don't actually remember the vast majority of them. I mean, I remember bits and pieces of
everything—high school, college, work, vacations, summers, and so on—but the specifics
of any given day? Kind of hit or miss.</p>
<p>That's how memory works, of course. If my life were a novel I'd be the least reliable narrator.</p>
<p>Today's 10,000 milestone feels particularly profound because of its timing. Last Friday, I left
<a href="https://www.hireart.com">HireArt</a>, the (then) five-person startup I moved across the country to
join in 2013. It was an incredibly tough decision, and maybe in a future post I'll share more about
what led to the change. But for now, I want to talk a bit about time, and how we spend it.</p>
<p>If I were to divide up my life into slices of 1,000 days, the entire last slice—just under
three years—would belong to the job I just left. That's one tenth of my entire life thus far.
Almost a third of my twenties, which are, I am realizing, a very limited resource.</p>
<p>That's not to say it wasn't time well spent. The last 1,000 days were, by far, my most prolific as
a coder. I'm not sure how exactly I'd quantify my productivity, except that... I wrote a LOT of
code. Actually, scratch that. On net I deleted more lines than I added, which is something I'm
quite proud of. And, more than just producing it, I thought about code. A lot.</p>
<p>I hope to keep that up, but I also hope to make a few changes going forward. As productive as I
was, the last three years were probably the least healthy for me. For one, I spent far too much on
food deliveries. Not once did I go to the gym, or even go for a jog around the block. And, sadly, I
didn't phone my parents nearly often enough.</p>
<p>That's the downside of being so mentally engaged in work. You lose track of what's really, truly
important in life, and you make trade-offs that optimize for short-term goals. So for the next
1,000 days I'm aiming to be both productive and develop more healthy habits. We'll see how I do.</p>
<p>Here's the thing about those 1,000 day chunks. My entire adult life only began, at most, three
chunks ago. And my professional career? Two-ish chunks. Out of ten. Crazy, right? Another chunk and
I'll be 30. Ten more chunks brings me into my mid-fifties. I hope by then I'll still call myself a
programmer, but honestly I have no idea.</p>
<p>Suffice it to say, I'm both terrified and eager to see what the next 1,000 (and 10,000) days have
in store.</p>
Farewell, Jekyll2016-03-03T00:00:00+00:002016-03-03T00:00:00+00:00https://ngriffith.com/writing/farewell-jekyll/<p>If you're reading this post, it means I've successfully transitioned my blog—its styles,
layout, and all of its posts—over to a new blogging engine. Farewell,
<a href="http://jekyllrb.com">Jekyll</a>. Say hello to <a href="http://middlemanapp.com">Middleman</a>. Booyah.</p>
<p>Okay, so, not much has actually changed. Visually, <em>nothing</em> has changed, aside from a few minor
housekeeping tweaks. I literally copy-pasted most of what you see. And, from a high level, the site
is still served as a set of static, pre-generated files. The URLs are all the same. Even the CDN
configuration is the same.</p>
<p>But after having a great time building the Middleman-powered <a href="http://code.hireart.com">developer
blog</a> at my office, I just couldn't resist dusting off my own website and
giving it an internal makeover. (Make-under?) The resulting site, while outwardly identical to its
predecessor, has a codebase that is leaps and bounds better—both easier to navigate and to
extend. And that's why I'm so excited.</p>
<h3 id="the-code">The Code</h3>
<p>Git tells me that I've cut the site down by about 7,600 lines of code:</p>
<p><img src="https://ngriffith.com/writing/farewell-jekyll/jekyll-middleman-git-stats.png" alt="Git Stats" /></p>
<p>Crazy right? Well, to be fair, most of that code went totally unused—left over from numerous
themes and plugins that I tested out and never bothered to remove. On its own, Jekyll is actually
quite compact.</p>
<p>Shortly after launching this site in 2012, I designed my own theme and pared the plugins down to a
small handful—such as one that <a href="https://github.com/scottwb/jekyll-tweet-tag">caches tweet
data</a> so that embedded tweets don't disappear months
or years later. Even with the plugins, the actual site was extremely tiny. The time I spent
simplifying things made it quite easy to wipe everything away and build the site again from
scratch.</p>
<h3 id="jekyll-vs-middleman">Jekyll vs. Middleman</h3>
<p>Would I recommend <a href="http://middlemanapp.com">Middleman</a> over <a href="http://jekyllrb.com">Jekyll</a> any day?
Well, not necessarily, but only because they're both so similar.</p>
<p>There is definitely a strong case to be made for the way Middleman has extracted its blogging
capabilities into a <a href="https://github.com/middleman/middleman-blog">separate gem</a>. It also keeps your
public-facing files encapsulated in a "source" folder, which is nice. For some reason, Jekyll mixes
public files with your private build files, requiring special configuration to prevent your site
from <a href="https://github.com/jekyll/jekyll/issues/906">leaking things</a> you didn't intend. Yuck.</p>
<p>Of course, Jekyll is by far the most popular, especially when it comes to blogging, and has made
numerous improvements since my use of it in 2012. So if you generally bet on the crowd favorite,
Jekyll is the way to go. Yet, Middleman remains a strong contender and its flexibility might be
more to your liking.</p>
freemail2015-09-02T00:00:00+00:002015-09-02T00:00:00+00:00https://ngriffith.com/projects/freemail/require_callbacks2015-09-01T00:00:00+00:002015-09-01T00:00:00+00:00https://ngriffith.com/projects/require-callbacks/auto_timezone2015-06-02T00:00:00+00:002015-06-02T00:00:00+00:00https://ngriffith.com/projects/auto-timezone/sojourn2015-03-02T00:00:00+00:002015-03-02T00:00:00+00:00https://ngriffith.com/projects/sojourn/omniauth-splitwise2014-01-02T00:00:00+00:002014-01-02T00:00:00+00:00https://ngriffith.com/projects/omniauth-splitwise/datamapper-money2012-11-04T00:00:00+00:002012-11-04T00:00:00+00:00https://ngriffith.com/projects/datamapper-money/"PushState or Bust"2012-02-20T00:00:00+00:002012-02-20T00:00:00+00:00https://ngriffith.com/writing/hash-banging-the-intertubes/<p>This post was originally going to be titled <em>Why is Twitter Still Hash-Banging Away?</em>, because I've
long been wondering why Twitter has not dropped the hash-bang—that ugly '<code>#!</code>'—from its
URLs. My question was answered earlier today when Dan Webb
<a href="https://twitter.com/danwrong/status/171681426297729025">tweeted</a> that Twitter would be getting rid
of its hash-bangs once and for all:</p>
<div class="embed tweet">
<blockquote class="twitter-tweet tw-align-center">
<p>@timhaines correct. All gone. It was a mistake for several reasons. PushState or bust.</p> — Dan Webb (@danwrong)
<a href="https://twitter.com/danwrong/status/171681426297729025" data-datetime="2012-02-20T19:43:36+00:00">February 20, 2012</a>
</blockquote>
<script src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
</div>
<p>The story of the hash-bang goes back to the early days of 'AJAXed' websites. (For the uninitiated,
'AJAX'—which stands for <em>Asynchronous Javascript and XML</em>—is a technique used to load
and display new content without reloading the rest of the page.) Back then, most browsers had a
security restriction which prevented sites from using Javascript to change the URL in the address
bar, which meant that jumping from <strong>example.com/page-one</strong> to <strong>example.com/page-two</strong> was not
possible using AJAX.</p>
<p><q class="right">'AJAX' is a misnomer—It need not be asynchronous, nor use XML, nor
even be written in Javascript</q> However, a portion of the URL called the <a href="http://en.wikipedia.org/wiki/Fragment_identifier">fragment
identifier</a>—anything that comes after a
'<code>#</code>'—could be modified without requiring a page reload. As a result, many sites began
inserting the '<code>#</code>' into each page's URL, which got the job done despite a few quirks. There was
just one problem—most of these pages were no longer crawlable by search engine spiders, as
they typically ignored any Javascript.</p>
<p>Google saw this and had an idea for saving these poor, lost websites. They came up with a long
<a href="https://developers.google.com/webmasters/ajax-crawling/docs/getting-started">specification</a> for
web developers, introducing a new crawling scheme for AJAXed websites. By using hash-bangs (<code>#!</code>)
instead of hashes (<code>#</code>), developers could indicate that their site supported this scheme and could
be indexed like a normal website. The catch was that all URLs would need to work properly when the
nonsensical string '<code>?_escaped_fragment_=</code>' was inserted in place of the hash-bang. Normal visitors
would never have to see this behavior, of course, but it was important for playing nice with search
engines.</p>
<p><q>Sounds a bit convoluted? It is.</q></p>
<p>The web was going through some serious growing pains, and Google's solution was complex and
awkward, despite its rapid adoption. Before long, a web developer named Mike Davies posted a
<a href="http://isolani.co.uk/blog/javascript/BreakingTheWebWithHashBangs/">rant</a> which argued that sites
making use of hash-bangs were actually "breaking the web." He was not alone, and many others
followed with similar arguments.</p>
<p>Perhaps one of the worst offenders in <em>hash-bangery</em>, as Davies pointed out, was <a href="http://gawker.com">Gawker
Media</a>, whose web designers implemented a jarring, AJAXed redesign across their
network of weblogs. Visitors <em>despised</em> the site's new behavior, and many soon discovered that
traditional URLs were quite literally <em>broken</em> when Javascript was turned off. Instead of
rolling-back the changes, Gawker wore its new, trendy layout with pride, and consoled frustrated
readers by creating a "Gawker Redesign" <a href="http://web.archive.org/web/20120225032845/http://gawker.com/redesign/forum">support
forum</a>.</p>
<p>Twitter jumped on the bandwagon as well, with what became known as #NewTwitter—not to be
confused with the more recent #NewNewTwitter—and visitors immediately started complaining
that the site was 'hijacking' their back buttons. In reality, Twitter was adding an extra page load
to inject the <code>#!</code> into its URLs, which did not play nicely with browsers' page histories. The
resulting 'fix' actually came from the browsers themselves, and not from Twitter.</p>
<p><q class="right">the hash-bang didn't break Gawker.<br/>Gawker Media broke themselves.</q> All
of this voodoo-hackery was, some argued, a necessary part of the modern web. After all, how else
could web designers create AJAXed pages with proper URLs? There were plenty of sites making
perfectly innocent use of hash-bangs, without any audience outcry.</p>
<p>The argument was finally put to rest in mid-2011 when the History API, also referred to as
'PushState,' came sailing along on a wave of other HTML5 features. By removing security
restrictions and allowing sites to modify their URLs on the fly, the History API fixed the problem
at its source. No longer were <code>#</code>'s or <code>#!</code>'s required for sites to perform their AJAX magic. The
web had finally completed its long-drawn transition from a dull repository of static pages to a
world of rich, interactive <em>applications</em>.</p>
<p>And that brings us back to Twitter. Why, exactly, has it taken almost a year for one of the most
influential sites of its time, a bright and shining beacon of the modern web, to acknowledge this
obvious feature? And when will they finally implement it? The aforementioned #NewNewTwitter came
and went without any indication that the plague of <code>#!</code>'s would ever go away. It is long past time
for Twitter to stop mangling its URLs with an outdated hack.</p>
LoveNotes 2.02012-02-18T00:00:00+00:002012-02-18T00:00:00+00:00https://ngriffith.com/projects/lovenotes/<p>Last year, <a href="http://quartey.tumblr.com">a friend</a> (and fellow alumnus of
<a href="http://yaleootb.com">OOTB</a>) came to me only days before Valentine's Day with the
idea for "LoveNotes," a little webapp for sending notes accompanied by OOTB's a cappella music. For
some crazy reason I said "yes," and we managed to get something out the door just in time. The
service was moderately successful among friends & family and helped promote OOTB to a wider
audience, so we came away feeling pretty satisfied.</p>
<p><q>Who knew how easy it could be to set‑up a simple e‑card service, right?</q></p>
<p>Well, last week I booted-up LoveNotes to see if I could tweak it a bit for 2012 and was floored by
how <em>uninviting</em> I found the site's design. Only a year had passed since its release, and already I
found myself wondering how I could have put together such an awkward layout. That's not to say it
was <em>entirely</em> bad—there are parts of the site that I thought came out pretty well—but
I guess you can take a look and judge for yourself:</p>
<div class="image">
<a href="lovenotes_before.jpg" target="_blank">
<image src="lovenotes_before_thumb.jpg" alt="Lovenotes 1.0" />
</a>
<br/>
The original LoveNotes
</div>
<p>I'm probably being too harsh on myself, but this was a big letdown for me. I remembered putting a
lot of detail-work into the site, and now I was considering cutting the 2012 release entirely. I
simply wasn't satisfied with the work I had done.</p>
<p>So I ended up scrapping most of it and starting from scratch. Once again, my deadline was only days
away, so to keep matters simple I settled on the visual theme of a postcard. Most of the redesign
came together on Sunday, and then the evening before Valentine's Day was spent polishing the
details and responding to feedback. Before I knew it, the new version was complete:</p>
<div class="image">
<a href="lovenotes_after.jpg" target="_blank">
<image src="lovenotes_after_thumb.jpg" alt="Lovenotes 2.0" />
</a>
<br/>
And thus, <a href="http://yaleootb.com/lovenotes" target="_blank">LoveNotes 2.0</a> was born.
</div>
<p>And how did it do? On Valentine's Day, LoveNotes saw 761 unique visitors from 10 countries and 40
states, for a total of 2,030 page views. We're not talking gone-viral traffic, but for a short
period there were 10-15 hits coming in per minute, which was exciting to watch. At one point Deke
Sharon, a well-known figure in the contemporary a cappella community,
<a href="http://twitter.com/dekesharon/status/169550965991686144">tweeted</a> about the app.
For a group of 17 college students, being able to connect to such a large audience in a single day
is pretty impressive. In all, not too shabby, especially given the short couple of days it took to
put it all together.</p>
<h2 id="a-few-takeaways">A Few Takeaways</h2>
<h3 id="begin-with-a-clear-concept">Begin with a clear concept</h3>
<p>The difference between this year and the previous—aside from my own web design skills
hopefully having improved a bit—was the fact that I had a clear concept to go on. Last year's
product was a complete jumble of mismatched ideas, despite the amount of time I spent on the
details. This year's postcard concept helped unify everything and led to some fun embellishments.</p>
<h3 id="email-is-a-harsh-mistress">Email is a harsh mistress</h3>
<p>Even if you <a href="http://37signals.com/svn/posts/3096-giving-away-the-secrets-of-993-email-delivery">do everything
right</a>,
you'll never see every email go through. I have no idea what percentage of the app's emails were
automatically flagged as spam last year, but I suspect that it was above 25%, given the number of
questions people asked. This year I made the long-overdue switch to
<a href="http://sendgrid.com/">SendGrid</a>, which brought the rate of successful delivery
to 92.5%. Not bad for only 15 minutes of setup work, but the site still got rejected by a couple of
email providers.</p>
<h3 id="anticipate-common-user-mistakes">Anticipate common user mistakes</h3>
<p>One easy step I overlooked (both this year and last) was to make sure that emails <em>look</em> valid
before accepting them. A <em>valid-looking</em> email might still bounce, but at least I could have asked
visitors to correct simple mistakes like forgetting the <em>dot</em> in '.com'.</p>
<h2 id="what-next">What Next?</h2>
<p>Although I probably won't rework <a href="http://yaleootb.com/lovenotes">LoveNotes</a> a
third time, I definitely see no reason to take it offline anytime soon. It may even see some new
song options in the future. For now, feel free to send LoveNotes to your heart's content, and let
me know how it goes!</p>
Hello World2012-02-16T00:00:00+00:002012-02-16T00:00:00+00:00https://ngriffith.com/writing/hello-world/<p>This is my obligatory 'hello world' post. For this site's engine I chose
<a href="http://github.com/mojombo/jekyll">Jekyll</a>, which is currently very fashionable among developers.
(I enjoy the concept of blogging the same way I code).</p>
<p>Expect an actual post sometime soon. For now, enjoy these relevant code examples:</p>
<h4 id="perl">perl</h4>
<pre style="background-color:#2b303b;">
<code class="language-perl" data-lang="perl"><span style="color:#96b5b4;">print </span><span style="color:#c0c5ce;">"</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#96b5b4;">\n</span><span style="color:#c0c5ce;">";
</span></code></pre><h4 id="ruby">ruby</h4>
<pre style="background-color:#2b303b;">
<code class="language-ruby" data-lang="ruby"><span style="color:#96b5b4;">puts </span><span style="color:#c0c5ce;">'</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#c0c5ce;">'
</span></code></pre><h4 id="python">python</h4>
<pre style="background-color:#2b303b;">
<code class="language-python" data-lang="python"><span style="color:#b48ead;">print </span><span style="color:#c0c5ce;">"</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#c0c5ce;">"
</span></code></pre><h4 id="javascript">javascript</h4>
<pre style="background-color:#2b303b;">
<code class="language-javascript" data-lang="javascript"><span style="color:#c0c5ce;">document.</span><span style="color:#96b5b4;">write</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#c0c5ce;">");
</span></code></pre><h4 id="haskell">haskell</h4>
<pre style="background-color:#2b303b;">
<code class="language-haskell" data-lang="haskell"><span style="color:#8fa1b3;">main </span><span style="color:#b48ead;">:: IO </span><span style="color:#c0c5ce;">()
main = putStrLn "</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#c0c5ce;">"
</span></code></pre><h4 id="c">c</h4>
<pre style="background-color:#2b303b;">
<code class="language-c" data-lang="c"><span style="color:#b48ead;">int </span><span style="color:#8fa1b3;">main</span><span style="color:#c0c5ce;">(</span><span style="color:#b48ead;">void</span><span style="color:#c0c5ce;">)
{
</span><span style="color:#96b5b4;">printf</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#96b5b4;">\n</span><span style="color:#c0c5ce;">");
</span><span style="color:#b48ead;">return </span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">;
}
</span></code></pre><h4 id="c-1">c++</h4>
<pre style="background-color:#2b303b;">
<code class="language-cpp" data-lang="cpp"><span style="color:#b48ead;">int </span><span style="color:#8fa1b3;">main</span><span style="color:#c0c5ce;">()
{
std::cout << "</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#96b5b4;">\n</span><span style="color:#c0c5ce;">";
}
</span></code></pre><h4 id="c-2">c#</h4>
<pre style="background-color:#2b303b;">
<code class="language-c#" data-lang="c#"><span style="color:#b48ead;">public class </span><span style="color:#ebcb8b;">HelloWorld
</span><span style="color:#eff1f5;">{
</span><span style="color:#b48ead;">static void </span><span style="color:#8fa1b3;">Main</span><span style="color:#eff1f5;">()
{
</span><span style="color:#bf616a;">System</span><span style="color:#eff1f5;">.</span><span style="color:#bf616a;">Console</span><span style="color:#eff1f5;">.</span><span style="color:#bf616a;">WriteLine</span><span style="color:#eff1f5;">(</span><span style="color:#c0c5ce;">"</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#c0c5ce;">"</span><span style="color:#eff1f5;">);
}
}
</span></code></pre><h4 id="java">java</h4>
<pre style="background-color:#2b303b;">
<code class="language-java" data-lang="java"><span style="color:#b48ead;">public class </span><span style="color:#ebcb8b;">HelloWorld </span><span style="color:#eff1f5;">{
</span><span style="color:#b48ead;">public static void </span><span style="color:#8fa1b3;">main</span><span style="color:#eff1f5;">(</span><span style="color:#ebcb8b;">String</span><span style="color:#b48ead;">[] </span><span style="color:#bf616a;">args</span><span style="color:#eff1f5;">) {
</span><span style="color:#ebcb8b;">System</span><span style="color:#eff1f5;">.out.</span><span style="color:#bf616a;">println</span><span style="color:#eff1f5;">(</span><span style="color:#c0c5ce;">"</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#c0c5ce;">"</span><span style="color:#eff1f5;">);
}
}
</span></code></pre><h4 id="go">go</h4>
<pre style="background-color:#2b303b;">
<code class="language-go" data-lang="go"><span style="color:#b48ead;">func </span><span style="color:#8fa1b3;">main</span><span style="color:#c0c5ce;">() {
</span><span style="color:#bf616a;">fmt</span><span style="color:#c0c5ce;">.</span><span style="color:#bf616a;">Println</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#c0c5ce;">")
}
</span></code></pre><h4 id="dart">dart</h4>
<p><a href="https://ngriffith.com/writing/hello-world/hello-world-in-dart.jpg">I don't always write "Hello World!" in dart, but when I do...</a></p>
<h4 id="lisp">lisp</h4>
<pre style="background-color:#2b303b;">
<code class="language-lisp" data-lang="lisp"><span style="color:#c0c5ce;">(</span><span style="color:#96b5b4;">format </span><span style="color:#d08770;">t </span><span style="color:#c0c5ce;">"</span><span style="color:#a3be8c;">Hello World!~%</span><span style="color:#c0c5ce;">")
</span></code></pre><h4 id="erlang">erlang</h4>
<pre style="background-color:#2b303b;">
<code class="language-erlang" data-lang="erlang"><span style="color:#8fa1b3;">hello_world</span><span style="color:#c0c5ce;">() -> </span><span style="color:#bf616a;">io</span><span style="color:#c0c5ce;">:</span><span style="color:#bf616a;">fwrite</span><span style="color:#c0c5ce;">("</span><span style="color:#a3be8c;">Hello World!</span><span style="color:#96b5b4;">\n</span><span style="color:#c0c5ce;">").
</span></code></pre><h4 id="gml">gml</h4>
<pre style="background-color:#2b303b;">
<code class="language-gml" data-lang="gml"><span style="color:#c0c5ce;">draw_text(0, 0, "Hello World!")
</span></code></pre><h4 id="html">html</h4>
<pre style="background-color:#2b303b;">
<code class="language-html" data-lang="html"><span style="color:#c0c5ce;"><!</span><span style="color:#b48ead;">DOCTYPE </span><span style="color:#d08770;">html</span><span style="color:#c0c5ce;">>
<</span><span style="color:#bf616a;">html</span><span style="color:#c0c5ce;">>
<</span><span style="color:#bf616a;">head</span><span style="color:#c0c5ce;">>
<</span><span style="color:#bf616a;">title</span><span style="color:#c0c5ce;">>Hello World!</</span><span style="color:#bf616a;">title</span><span style="color:#c0c5ce;">>
</</span><span style="color:#bf616a;">head</span><span style="color:#c0c5ce;">>
<</span><span style="color:#bf616a;">body</span><span style="color:#c0c5ce;">>
<</span><span style="color:#bf616a;">p</span><span style="color:#c0c5ce;">>Hello World!</</span><span style="color:#bf616a;">p</span><span style="color:#c0c5ce;">>
</</span><span style="color:#bf616a;">body</span><span style="color:#c0c5ce;">>
</</span><span style="color:#bf616a;">html</span><span style="color:#c0c5ce;">>
</span></code></pre>YaleOOTB.com2010-08-30T00:00:00+00:002010-08-30T00:00:00+00:00https://ngriffith.com/projects/yaleootb-dot-com/