<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Caleb Schoepp</title><link>/</link><description>Recent content from Caleb Schoepp</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 14 Mar 2020 21:19:43 -0700</lastBuildDate><atom:link href="/index.xml" rel="self" type="application/rss+xml"/><item><title>Year in Review: 2025</title><link>/blog/2025/year-in-review-2025/</link><pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate><guid>/blog/2025/year-in-review-2025/</guid><description>&lt;p>Here is the 2025 edition of my year-in-review post. This means I&amp;rsquo;ve been doing this for six years now (&lt;a href="/blog/2020/year-in-review-2020">2020&lt;/a>, &lt;a href="/blog/2021/year-in-review-2021">2021&lt;/a>, &lt;a href="/blog/2022/year-in-review-2022">2022&lt;/a>, &lt;a href="/blog/2023/year-in-review-2023">2023&lt;/a>, &lt;a href="/blog/2024/year-in-review-2024">2024&lt;/a>)!&lt;/p>
&lt;h2 id="top-10-experiences">Top 10 Experiences&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>RIZN&lt;/strong> — Serving as a RIZN leader has been such a blessing in my life. I gained a new community, made amazing memories, and had an impact on the youth. Highlights include RIZN Camp, winning Water War, World&amp;rsquo;s Best Saturday, slip n&amp;rsquo; slide kickball, and Fear Factor.&lt;/li>
&lt;li>&lt;strong>Marathon in Victoria&lt;/strong> — Finishing my first marathon was the hardest thing I&amp;rsquo;ve ever done. Finishing the race was a deeply emotional experience. I laughed, I cried, I grimaced and I rejoiced.&lt;/li>
&lt;li>&lt;strong>Trip to France and Italy&lt;/strong> — I explored Milan, Turin, Nice, Villefranche-sur Mer, Monaco, and Paris. One of my co-workers joined me for part of the trip which I really enjoyed — double the pastries! This was during the peak weeks leading up to my marathon so I mostly ran and ate good food.&lt;/li>
&lt;li>&lt;strong>Trip to Barcelona and London&lt;/strong> — I liked Barcelona so much more the second time around.&lt;/li>
&lt;li>&lt;strong>Triathlons&lt;/strong> — A big focus of mine this year was training for triathlons. I loved pushing myself, training with friends and family, and competing.&lt;/li>
&lt;li>&lt;strong>Cancun offsite&lt;/strong> — I love Fermyon offsites and a few of us from the team went scuba diving together.&lt;/li>
&lt;li>&lt;strong>Trip to Austin&lt;/strong> — A quintessential Texas experience: I shot some full auto rifles at a gun range, swam in Barton springs, and ate delicious BBQ.&lt;/li>
&lt;li>&lt;strong>Vancouver trip&lt;/strong> — Initially this trip was bittersweet because I was meant to be doing the Vancouver marathon but due to injury was only doing the 8km race. However, I had such a good time with my sister, brother-in-law, and their friends.&lt;/li>
&lt;li>&lt;strong>Jamming with friends&lt;/strong> — The group of folks I jam with has grown and our jams are fun as ever. Shoutout to Eric for letting me learn on his drums.&lt;/li>
&lt;li>&lt;strong>Trip to Boulder&lt;/strong> — Some Fermyon friends and I rented a beautiful AirBnB in Boulder and spent a week co-working. The hot tub and runs together were my highlights.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to my &lt;strong>Atlanta trip&lt;/strong>, &lt;strong>Montreal trip&lt;/strong>, &lt;strong>Activate&lt;/strong>, &lt;strong>selling 50/50 tickets in Oilers playoffs&lt;/strong>, &lt;strong>Farm Team winning the division&lt;/strong>, &lt;strong>opportunities to pray for strangers when traveling&lt;/strong>, &lt;strong>hosting dinner parties&lt;/strong>, &lt;strong>race car hot laps&lt;/strong>, &lt;strong>family time&lt;/strong>, and the &lt;strong>Donovan Woods Concert&lt;/strong>.&lt;/p>
&lt;h2 id="top-10-movies-and-tv-shows">Top 10 Movies and TV Shows&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Warfare&lt;/strong> — War is long periods of boredom punctuated by moments of extreme violence and sheer terror. This captures that better than any other mainstream movie I&amp;rsquo;ve seen.&lt;/li>
&lt;li>&lt;strong>The Wire&lt;/strong> — This holds up to all the acclaim it has received over the years. Glad I went back to watch this.&lt;/li>
&lt;li>&lt;strong>2000 Meters to Andriivka&lt;/strong> — Absolutely haunting. I had trouble sleeping for a few days after watching this. War is truly horrible.&lt;/li>
&lt;li>&lt;strong>F1&lt;/strong> — The POV shots on the track are incredible.&lt;/li>
&lt;li>&lt;strong>Tour de France: Unchained&lt;/strong> — This was a gateway drug for me into a short term obsession with the Tour de France. I can&amp;rsquo;t believe the kind of watts Tadej Pogačar puts out on those climbs.&lt;/li>
&lt;li>&lt;strong>Our Flag Means Death&lt;/strong> — Pirates meet safe spaces — hilarious.&lt;/li>
&lt;li>&lt;strong>Here&lt;/strong> — Coolest concept piece I saw this year.&lt;/li>
&lt;li>&lt;strong>The Peanut Butter Falcon&lt;/strong> — Heartwarming and funny. A rock solid movie.&lt;/li>
&lt;li>&lt;strong>Portlandia&lt;/strong> — One third of the skits flop, one third are funny, and one third are absolute genius with deep cutting commentary and gut wrenching laughs.&lt;/li>
&lt;li>&lt;strong>Ford v Ferrari&lt;/strong> — Just another good action flick.&lt;/li>
&lt;/ol>
&lt;p>Apparently I like racing movies and war movies. Who knew? Honorable mentions to &lt;strong>Boots&lt;/strong>, &lt;strong>Wake Up Dead Man: A Knives Out Mystery&lt;/strong>, &lt;strong>Last Breath&lt;/strong>, &lt;strong>Her&lt;/strong>, &lt;strong>Black Mirror S7E1&lt;/strong>, &lt;strong>Nobody Wants This&lt;/strong>, &lt;strong>Randy Feltface: Feltopia&lt;/strong>, and &lt;strong>The Big Sick&lt;/strong>.&lt;/p>
&lt;h2 id="top-10-books">Top 10 Books&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Em&amp;rsquo;s Mems&lt;/strong> — Before my granny passed away she wrote her memoirs. I had the honor of compiling these into a book for our family&lt;/li>
&lt;li>&lt;strong>A City on Mars&lt;/strong> — This completely changed my perspective on space colonization and it&amp;rsquo;s hilarious to boot.&lt;/li>
&lt;li>&lt;strong>Made for People&lt;/strong> — I crushed this as an audiobook as I did a single day trip to and from Calgary. Changed how I think about the most important friendships in my life and highlighted the importance of confession.&lt;/li>
&lt;li>&lt;strong>The Wealthy Barber&lt;/strong> — It was fun reading the revised edition. The Wealthy Barber was the first personal finance book I ever read (at a very young age when my Dad gave it to me) and it set me on the path to being financially literate. This is going in my repertoire of books I&amp;rsquo;ll buy for people along with the Problem of God by Mark Clark.&lt;/li>
&lt;li>&lt;strong>Extreme Ownership&lt;/strong> — I liked the leadership principles and the stories. I felt the examples of applying the leadership principles were cheesy.&lt;/li>
&lt;li>&lt;strong>Apollo Murders&lt;/strong> — This was a fun twist on the Apollo stories that I love learning about.&lt;/li>
&lt;li>&lt;strong>The Art of Spending&lt;/strong> — A nice fine tuning on my financial direction.&lt;/li>
&lt;li>&lt;strong>Liturgy of the Ordinary&lt;/strong> — My friend James loaned this to me. Quite the poetic read.&lt;/li>
&lt;li>&lt;strong>Same as Ever&lt;/strong> — I really like Morgan Housel&amp;rsquo;s writing and thinking, but truthfully I think this was his worst book yet.&lt;/li>
&lt;li>&lt;strong>Single, Dating, Engaged&lt;/strong> — A Christian framework for relationships.&lt;/li>
&lt;/ol>
&lt;h2 id="top-10-others">Top 10 Others&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Wahoo KICKR CORE 2 Smart Trainer&lt;/strong> — Absolute game changer for bike training during the long Canadian winters. No more crappy gym spin bikes for me.&lt;/li>
&lt;li>&lt;strong>Waymo&lt;/strong> — The future is already here – it’s just not very evenly distributed. After one minute in my first Waymo ride I was already comfortable and convinced this is the future of transportation.&lt;/li>
&lt;li>&lt;strong>Cube&lt;/strong> — I freaking love cube. The highlight of the year was getting to play in London Cube Clash 2 by total happenstance.&lt;/li>
&lt;li>&lt;strong>The Dream by The Favors&lt;/strong> — Ashe and Finneas are two of my favorite artists. Peanut butter and jelly.&lt;/li>
&lt;li>&lt;strong>Rational Reminder podcast&lt;/strong> — This is the best and most evidence based personal finance podcast out there. I also appreciate that it is Canadian focused.&lt;/li>
&lt;li>&lt;strong>Web comics&lt;/strong> — &lt;a href="https://www.smbc-comics.com/">Saturday Morning Breakfast Cereal&lt;/a>, &lt;a href="https://pbfcomics.com/">The Perry Bible Fellowship&lt;/a>, and &lt;a href="https://xkcd.com/">xkcd&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Oxide and Friends podcast&lt;/strong> — Deep tech insider baseball. Their sense of humor is incredible.&lt;/li>
&lt;li>&lt;strong>Tape Notes podcast&lt;/strong> — I love hearing how my favorite artists write and produce their music.&lt;/li>
&lt;li>&lt;strong>Acquired podcast&lt;/strong> — Acquired is a like fine wine that keeps getting better every year.&lt;/li>
&lt;li>&lt;strong>Thermapen One&lt;/strong> — It&amp;rsquo;s expensive, but it is the best instant read thermometer out there.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to &lt;strong>The Money Scope podcast&lt;/strong>, &lt;strong>The Crux by Djo&lt;/strong>, &lt;strong>Lord of the Flies &amp;amp; Birds &amp;amp; Bees by TALK&lt;/strong>, &lt;strong>Sean Goedecke&amp;rsquo;s blog&lt;/strong>, and &lt;strong>Hoka Clifton 9s&lt;/strong>.&lt;/p>
&lt;h2 id="reflecting-on-2025">Reflecting on 2025&lt;/h2>
&lt;h3 id="career">Career&lt;/h3>
&lt;blockquote>
&lt;p>My hypothesis is still that going to a co-working space would do wonders for my productivity and focus. This is going to be the year that I actually try one out.&lt;/p>&lt;/blockquote>
&lt;p>I once again failed to try out a co-working space so I think it is time to accept my proclivities and move on from that goal. I&amp;rsquo;m going to count the WASI Observe goal as accomplished. Technically it is called WASI OTel now, and it is only in phase 1, but we have some really good momentum and I think phase 2 is just around the corner. I ended up giving &lt;a href="/talks">four talks&lt;/a> this year which was a surprising boon. Over the Christmas break I got more reps with AI by vibe coding a &lt;a href="https://cook.calebschoepp.com">custom recipe app&lt;/a> for my unique needs. Fermyon also got &lt;a href="https://www.fermyon.com/blog/fermyon-joins-akamai">acquired&lt;/a> by Akamai this year and I got promoted to Senior Software Engineer. One last thing worth mentioning is that I built the habit of time tracking with &lt;a href="https://timeryapp.com/">Timery&lt;/a> which has made me more effective. Overall, a good year career-wise.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Maintain a sustainable work-life balance.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Try out a co-working space.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Advance WASI Observe to phase 2.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Attend some local software engineering meetups.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Give another conference talk.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Build, publish, and write about one meaningful personal project that contributes to my career trajectory.&lt;/li>
&lt;/ul>
&lt;h3 id="digital-life">Digital Life&lt;/h3>
&lt;blockquote>
&lt;p>This is a new category that is the amalgamation of organization and mental health. It&amp;rsquo;s mostly just a bunch of shots in the dark about how I think I could improve my relationship with my phone.&lt;/p>&lt;/blockquote>
&lt;p>The biggest thing to point out here is that I&amp;rsquo;ve completely stopped using Instagram and YouTube on my phone which has felt really healthy. I&amp;rsquo;ve also kneecapped YouTube&amp;rsquo;s grip on me by using &lt;a href="https://unhook.app/">Unhook&lt;/a>. My biggest problem is still Instagram. Even when I use it on my computer for a reasonable amount of time I find it makes me feel icky afterwards due to comparison.&lt;/p>
&lt;p>By charging my phone outside my bedroom I do a good job of not using my phone immediately in the morning. But, I struggle with using my phone right until the moment I go to bed.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep making use of Todoist and Obsidian.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Don&amp;rsquo;t use my phone between 9pm and 9am.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Spend one Sunday a month not using digital devices.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Try not using social media for a month.&lt;/li>
&lt;/ul>
&lt;h3 id="faith">Faith&lt;/h3>
&lt;blockquote>
&lt;p>My main focus this year is around growing in my discipleship. This looks like creating habits where I spend meaningful time praying and in scripture.&lt;/p>&lt;/blockquote>
&lt;p>I read 15 books of the Bible of the year which I&amp;rsquo;m quite happy with. On average across the year I probably managed two to three mornings a week of prayer and reading my Bible. I also did a half day of solitude when I was in Milan. From a quantitative perspective I mostly failed, but qualitatively I&amp;rsquo;m very happy with my growth in discipleship this year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build a habit of reading the Bible and praying four mornings a week.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Read 12 books of the Bible.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Spend one Sunday a month sabbathing.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Spend one hour a week in solitude.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Do a weekend-long solitude retreat.&lt;/li>
&lt;/ul>
&lt;h3 id="finances">Finances&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;m very blessed that most of my financial goals feel like they&amp;rsquo;re just on auto-pilot. The most pressing concern is to continue to fine-tune how I&amp;rsquo;m deploying my capital. That may or may not look like buying more real estate.&lt;/p>&lt;/blockquote>
&lt;p>I still love YNAB. It gets more fun every year as I have more and more spending and net worth history. Since I started using the Wealthsimple credit card I get mobile notifications for purchases which has been really helpful for keeping on top of entering transactions into YNAB. Speaking of Wealthsimple, I went all in on using it for my saving, checking, credit, and investing needs. I&amp;rsquo;ve been loving the consolidated solution and Wealthsimple is net better than traditional banks on every single dimension in my experience. The only double edged sword is the amazing visualization around your investments. On the one hand you have pretty graphs and on the other hand it is way too tempting to stare at your portfolio.&lt;/p>
&lt;p>I&amp;rsquo;ve consolidated my investments to a simple strategy of holding Vanguard all in one asset allocation ETFs. Influenced by my obsession with Rational Reminder (specifically their presentation of Scott Cederburg&amp;rsquo;s work) I&amp;rsquo;ve opted for a 100% stock allocation. This most closely matches my risk tolerance — high tolerance for volatility and low tolerance for not meeting my goals long term. I&amp;rsquo;d rather water down my returns with healthy reserves of cash rather than bonds. I will need to remain psychologically vigilant to not kneecap myself when I encounter my first proper downturn.&lt;/p>
&lt;p>Throughout the year, I saved 28.8% of my gross income (42.4% net income) and donated 8.0% of my gross income (11.8% net income). My current asset allocation is as follows: 72.8% equities, 0% bonds, 3.5% crypto, 11.0% real estate, and 12.7% cash&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>My rental property (home) is just starting to break even on operating cashflow and is slightly negative in terms of free cashflow. This is due to me putting down so little on the home. Throughout the year I&amp;rsquo;ve come to the conclusion that I&amp;rsquo;m content with investing in equities and don&amp;rsquo;t need to rush into more real estate yet. I like where I live; real estate is more work and more stress than buying an ETF; I&amp;rsquo;m not convinced I need the leverage; and I&amp;rsquo;m not convinced real estate returns will be significantly better in the long term. This isn&amp;rsquo;t to say I don&amp;rsquo;t want to own more real estate — just that I&amp;rsquo;m in much less of a rush to do so.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Use a budgeting tool like &lt;a href="https://ynab.com">YNAB&lt;/a> all year.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Save 25% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Donate 10% of my gross income.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Explore the opportunity of building a laneway suite in my backyard.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Have a down payment to purchase a rental property in 2026.&lt;/li>
&lt;/ul>
&lt;h3 id="music">Music&lt;/h3>
&lt;blockquote>
&lt;p>Continuing the theme of last year, I want to focus more on mastering piano rather than guitar. I&amp;rsquo;ve laid out no specific theory goals, but that is because I&amp;rsquo;m naturally learning the theory as I progress on piano.&lt;/p>&lt;/blockquote>
&lt;p>I made lots of progress on piano this year. I wrote my RCM level 1 exam in January and I&amp;rsquo;ll be writing my RCM level 3 exam in a few weeks. This rapid progress in piano stemmed directly from my daily commitment to practice. The tradeoff is that I felt the slight atrophy of my guitar skills. I still noodle a lot on my guitar, but I didn&amp;rsquo;t practice at all with it this year — it showed.&lt;/p>
&lt;p>Jam sessions with friends continued to play a central role in my musical life and our crew is growing! I even had a few opportunities to start playing the drums and I&amp;rsquo;m not half bad.&lt;/p>
&lt;p>Same as last year I played on the Bethel worship team a few times. I even started joining the Celebration worship team which I was proud because they require total memorization. However, I had to back away from this commitment due to a lack of time.&lt;/p>
&lt;p>Melodies, chord progressions, lyrics, and ideas for songs continue to pour out of me — the 64 voice notes on my phone from the year are a testament to this. However, I had little time or energy this year for any mixing, mastering, or producing.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Take piano lessons all year and perform in recitals.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Practice piano daily.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Complete RCM piano level 2.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Organize jam sessions with friends.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Release three mixed and mastered songs on SoundCloud.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Master the A shape barre chord on guitar.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Join the worship team at Celebration.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Complete Justin Guitar Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Organize and perform a small concert at a senior center over Christmas.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Learn to use a looper with &lt;a href="https://learnpracticeplay.com/loop-school-signup/">this&lt;/a> course.&lt;/li>
&lt;/ul>
&lt;h3 id="physical-health">Physical Health&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;ve realized that completing an Ironman and doing a muscle-up are two lifelong fitness dreams I have. I may never do them, but I want to try. My goals this year should help point me in that direction.
&lt;br>&lt;br>
The other big thrust I want to make is to clean up my diet. I already eat fairly healthy, but I have very little discipline around my diet. I want to improve the following things: First, focus on my calorie deficit/excess and be able to consciously control it. Second, make it a habit of eating enough protein. Third, make the hard decisions at the grocery store — if I don&amp;rsquo;t bring junk home, I only have to avoid it once.&lt;/p>&lt;/blockquote>
&lt;p>The biggest difference in 2025 was my renewed commitment to physical fitness. I think I&amp;rsquo;m in the best shape of my life right now and it is only improving. I started the year off by hiring a triathlon coach. This was instrumental in multiple ways: it held me accountable; reduced the logistical overhead of training plans; help me set realistic expectations; and let me glean from his decades of racing experience. I went really hard this year and was training 6-7 days a week all year. Some days were doubles and I often had a hockey game each week too. This was exhausting but the gains were tangible.&lt;/p>
&lt;p>Throughout the year I competed in lots of races. Two sprint triathlons, one olympic triathlon, an 8km run, a marathon, and a beer mile with friends. I also participated in two triathlon focused swim camps where I saw big gains. I&amp;rsquo;m happy to say that I&amp;rsquo;ve completed all individual parts of an Ironman — now I just need to do them together.&lt;/p>
&lt;p>Outside of endurance racing I also kept up with hockey this year playing in a higher division. It took awhile but I feel I&amp;rsquo;m finally getting my footing at the higher level. For example I&amp;rsquo;m playing more relaxed with the puck and not just coughing it up in a state of panic. I also played some pickleball this year which is the first racket sport I&amp;rsquo;ve really enjoyed.&lt;/p>
&lt;p>My diet didn&amp;rsquo;t significantly change this year. If anything I&amp;rsquo;m eating way more due to my training volume. Overall I think what I eat is healthy enough that I don&amp;rsquo;t think it will be a priority next year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Be able to do five pull-ups by the end of the year.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Do a strength workout at least once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do yoga every day for one month of the year.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Finish my first marathon.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Finish faster than my brother-in-law in an Olympic triathlon.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Play hockey once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Establish more disciplined eating habits.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Race a half Ironman late in the season.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take some power skating lessons.&lt;/li>
&lt;/ul>
&lt;h3 id="reading">Reading&lt;/h3>
&lt;blockquote>
&lt;p>Not much to say, let&amp;rsquo;s go read some books.&lt;/p>&lt;/blockquote>
&lt;p>Barely managed to get this one done.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Read 10+ books.&lt;/li>
&lt;/ul>
&lt;h3 id="relationships">Relationships&lt;/h3>
&lt;blockquote>
&lt;p>These are pretty similar goals to last year with the caveat that I&amp;rsquo;m setting goals that are easier to objectively mark. I hope this will also make them easier to build into my life schedule.&lt;/p>&lt;/blockquote>
&lt;p>I&amp;rsquo;m really happy with how this year went in terms of relationships with my friends and family. I feel that I put a healthy emphasis on maintaining and growing these relationships. I&amp;rsquo;m also pleased that my circle of friendships grew a lot this year with the community I made through RIZN. A hope of mine this year was to meet someone, but that will come with time.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Prioritize and maintain relationships with my family and close friends.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Schedule lunches with friends and family in town once a month.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Hop on a call with a friend abroad once a month.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Host four dinner parties.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure">Travel and Adventure&lt;/h3>
&lt;blockquote>
&lt;p>Once again, I&amp;rsquo;m just listing a bunch of stretch goals as inspiration. My travel usually ends up being spontaneous or based on where I travel for work anyway.&lt;/p>&lt;/blockquote>
&lt;p>While I technically met none of my goals here I still had a very adventurous year by any standard. Just look at my top 10 experiences!&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Family canoeing trip in Bowron Lake Provincial Park.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Go to New York.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take dance lessons.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Travel Asia.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Visit family in New Zealand.&lt;/li>
&lt;/ul>
&lt;h3 id="writing">Writing&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;m setting a less ambitious writing goal this year because I&amp;rsquo;m finding it to be less of a priority in my life. I don&amp;rsquo;t feel the need to write just for the sake of writing. I do, however, want to keep publishing thoughtful pieces periodically.&lt;/p>&lt;/blockquote>
&lt;p>Writing for my blog fell through the cracks as I focused on executing on my daily career, musical, relational, and physical goals. I did finish editing and publish my granny&amp;rsquo;s memoirs though, which I&amp;rsquo;m immensely proud of.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 4+ non-personal blog posts.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Finish editing my grandmother&amp;rsquo;s stories and publish them.&lt;/li>
&lt;/ul>
&lt;h2 id="looking-forward-to-2026">Looking Forward to 2026&lt;/h2>
&lt;h3 id="career-1">Career&lt;/h3>
&lt;p>Fermyon was just acquired by Akamai so I&amp;rsquo;ll be running the &amp;ldquo;new job&amp;rdquo; &lt;a href="/blog/2022/6-tips-for-starting-a-software-engineering-internship/">playbook&lt;/a>.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Maintain a sustainable work-life balance.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Advance WASI OTel to phase 2 and land support in Spin.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Network within Akamai and have 1-1&amp;rsquo;s with 20 new people.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Spend more time outside the house working at coffee shops or the homes of friends and family.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Provide my manager with biweekly written &lt;a href="https://staysaasy.com/management/2025/12/21/ic-advice.html#send-regular-updates">updates&lt;/a>.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Continue tracking my &lt;a href="https://jvns.ca/blog/brag-documents/">brag document&lt;/a>.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Give another conference talk.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Build, publish, and write about two meaningful personal projects that contribute to my career trajectory.&lt;/li>
&lt;/ul>
&lt;h3 id="digital-life-1">Digital Life&lt;/h3>
&lt;p>Success here looks like less. Consolidate tools. Spend less time on social media. Spend less time on my phone.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Consolidate cloud storage on Dropbox or Google Drive.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Decide on how I want Obsidian and Google Keep to play together&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Run a tight ship with Todoist.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Continue to add recipes to my personal &lt;a href="https://cook.calebschoepp.com">recipe site&lt;/a>.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Charge my phone outside the bedroom.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Brick my phone between 9pm and 9am.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep Instagram and YouTube and other addictive social media off my phone.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Spend one Sunday a month not using digital devices.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Delete my Instagram account.&lt;/li>
&lt;/ul>
&lt;h3 id="faith-1">Faith&lt;/h3>
&lt;p>The themes for this year are discipleship and fellowship.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Continue with youth leading at RIZN.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Attend a Bible study or connect group weekly.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Build a habit of reading the Bible and praying four mornings a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Build a regular habit of fasting and praying once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do a three day water fast during Celebration&amp;rsquo;s week of prayer and fasting.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Finish my first read through of the Bible.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Spend one Sunday a month sabbathing.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Do a day-long solitude retreat.&lt;/li>
&lt;/ul>
&lt;h3 id="finances-1">Finances&lt;/h3>
&lt;p>Last year I did a really good job automating my finances, simplifying my strategy, and consolidating my accounts. This year I want to keep it on auto-pilot and codify my philosophy. A good year looks like making very few changes to my financial setup.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Use a budgeting tool like YNAB all year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Save at least 25% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Donate at least 10% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Cap my salary and allocate marginal dollars as follows: 40% invested, 40% donated, 20% for me.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Get a will and personal directive.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Write a personal investment policy statement&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Write a personal finance operating policy&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>.&lt;/li>
&lt;/ul>
&lt;h3 id="music-1">Music&lt;/h3>
&lt;p>I think I have at least one more year in me of wanting to prioritize piano over guitar.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Continue to create a back catalog of voice notes with song ideas.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Take piano lessons all year and perform in recitals.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Practice piano daily.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Pass level 4 RCM piano exam.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete RCM theory level 5.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Compose two piano pieces complete with sheet music.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Organize jam sessions with friends.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Complete Justin Guitar Grade 4.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Release one mixed and mastered song on SoundCloud.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Organize and perform a small concert at a senior center over Christmas.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Learn to use a looper with &lt;a href="https://learnpracticeplay.com/loop-school-signup/">this&lt;/a> course.&lt;/li>
&lt;/ul>
&lt;h3 id="physical-health-1">Physical Health&lt;/h3>
&lt;p>I&amp;rsquo;m aware that my goals in this category are a touch beyond insane, but hey a guy can dream right? Last year&amp;rsquo;s progress has left me feeling like I can tackle anything.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Play hockey once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep training hard for races guided by a triathlon coach&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup>.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Continue to lift heavy once a week even through triathlon training.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Finish faster than my brother-in-law in a half Ironman.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Finish an Ironman.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do leg 5 of the Sinister 7.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Run the Hypothermic Half Marathon.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Compete in local Aquathlons with chance to qualify for worlds.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Compete in SwimRun Edmonton race.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Train by myself less.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take some power skating lessons.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Be able to do a pullup by the end of the year.&lt;/li>
&lt;/ul>
&lt;h3 id="reading-1">Reading&lt;/h3>
&lt;p>Same goal as last year. It&amp;rsquo;s not ambitious, but rather is more of a baseline of how much I want to read.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 10+ books.&lt;/li>
&lt;/ul>
&lt;h3 id="relationships-1">Relationships&lt;/h3>
&lt;p>Last year I learned I really love hosting so I want to emphasize that this year. I also want to make sure I&amp;rsquo;m spending lots of time with friends. The logistics of finding a time to grab a coffee or workout together is annoying but it is so worth it.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Prioritize and maintain relationships with my family and close friends.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Schedule a coffee, lunch, or workout with a friend or family once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Host a block party BBQ.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Host 10+ casual dinner parties&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Be content in my season of singleness while being prepared to de-emphasize my other goals if I enter into a relationship.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Meet someone&lt;sup id="fnref:6">&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref">6&lt;/a>&lt;/sup>.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure-1">Travel and Adventure&lt;/h3>
&lt;p>Work travel is usually the impetus for my adventures and I&amp;rsquo;m not sure yet what work travel will look like with Akamai. I&amp;rsquo;ve included some more local goals to counteract this uncertainty.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Go skiing in the mountains for the first time.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Visit family in New Zealand.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Go hunting for the first time.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Work from the mountains for a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Visit New York.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take dance lessons.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Visit Asia.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Go bike packing.&lt;/li>
&lt;/ul>
&lt;h3 id="writing-1">Writing&lt;/h3>
&lt;p>I&amp;rsquo;m further shrinking my ambitions here to make room for other priorities in my life.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Continue to write about my race and training experiences.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 2+ non-personal blog posts.&lt;/li>
&lt;/ul>
&lt;h2 id="closing-thoughts">Closing Thoughts&lt;/h2>
&lt;p>Writing these reviews is always such a rich experience. I enjoy looking back at the year and reminiscing over everything I experienced and achieved. And I am better off for spending the time to set my goals for the coming year. I still struggle with the exact format of this review though. By it&amp;rsquo;s public nature there is a lot of private and emotional details that I have to leave out — but, I don&amp;rsquo;t have another place to textually process those things. I also sometimes wish my reflection was more &lt;a href="https://justinjackson.ca/2025-review">free flowing&lt;/a> and less list and goal oriented. Potential improvements for next year.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>This doesn’t represent my target allocation (which is quite different); it represents my actual allocation after any portfolio drift occurs. It’s difficult to accurately assess your current equity in a property, so I aimed conservatively 🤷. Cash represents my emergency fund and anything I’m holding liquid to later move into another asset class — it does not include money assigned to my regular budget.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>A complete list of my digital tools for posterity&amp;rsquo;s sake: Obsidian, Google Keep, Todoist, Gmail, Google Calendar, Google Recorder, Google Photos, YNAB, Goodreads, Feeder, Strava, and Hevy.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>A one pager to capture things like target allocation, risk tolerance, rebalancing rules, rules for changing strategy (job loss, marriage, kids, fear, etc.).&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>A one pager to capture things like budgeting philosophy, income cap, use of marginal dollars, charitable giving strategy, etc.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5">
&lt;p>Should I need to deprioritize this because I lose interest or need to focus on other things I would want to fall back to a minimum of three strength or cardio workouts a week.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:6">
&lt;p>This sounds like a outcome oriented goal, but I truly mean it in a process oriented sense. You can&amp;rsquo;t just control something like this.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>2025 Race Season</title><link>/blog/2025/2025-race-season/</link><pubDate>Sun, 28 Dec 2025 00:00:00 +0000</pubDate><guid>/blog/2025/2025-race-season/</guid><description>&lt;p>Traditionally I have written individual reports for each race that I&amp;rsquo;ve done. This year (partly due to laziness) I&amp;rsquo;m going to do a single post that summarizes my entire year of training and races. I&amp;rsquo;m hoping this might be a better format since it will capture the ups and downs of my year rather than isolated events.&lt;/p>
&lt;p>In 2024 I ran my first ever &lt;a href="/blog/2024/great-white-north-sprint-triathlon-race-report">triathlon&lt;/a> and quickly followed it with &lt;a href="/blog/2024/do-north-sprint-triathlon-race-report">another&lt;/a> — by the end of the year I ran my first ever &lt;a href="/blog/2024/run-the-river-half-marathon-race-report">half marathon&lt;/a>. I found the race atmosphere electrifying and more importantly I enjoyed discovering what my body and mind was capable of. I was hooked.&lt;/p>
&lt;p>In November of 2024 I had found a triathlon coach named Jason that I wanted to work with. The main reason I found a coach was because I was planning to do both an olympic triathlon and a marathon in 2025 and I had no clue how to balance training for both. Having a coach has proved very helpful in other ways: lower mental overhead from planning each week; someone to pose very technical training or race questions to; a small boost of external motivation each week; learning about training camps and races; and someone experienced to help properly manage my weekly load.&lt;/p>
&lt;p>Motivated from 2024&amp;rsquo;s races and with a coach by my side I started 2025&amp;rsquo;s training in earnest. I had two main goals for the year that I was working towards — the Vancouver Marathon and beating my brother-in-law Patrick in the Great White North Olympic Triathlon. The marathon was in May and the triathlon was in July. As such I started with a mix of strength, swimming, biking, and running training — but with a heavy focus on running.&lt;/p>
&lt;p>Another benefit of having a coach was the variety and specificity of my workouts. For example a swim session for me used to look like picking an arbitrary distance, say 800 m, and trying to just swim that straight up. But, with a coach my sessions had a focus e.g. technique or intervals. They would have structured warmups, main sets, cool downs, specific timing instructions, and more.&lt;/p>
&lt;p>Training with a coach also resulted in a marked increase in my weekly training volume. Previously I was training maybe five times a week. Following my coaches plan I was training maybe eight times a week. This feels normal to me now, but it took my body awhile to adjust to that big jump in volume.&lt;/p>
&lt;p>Speaking of volume because I had a marathon coming up in four months my running volume was climbing rapidly to amounts I had never run before. At first this felt great, but it quickly took a turn for the worse. I would come back from runs with my glutes feeling absolutely spent. I didn&amp;rsquo;t think much of it at the time, but with hindsight this was a clear sign that injury was right around the corner.&lt;/p>
&lt;p>On the first week of February during a company offsite in Cancun injury finally struck. After a run one morning the outside of my right knee felt like it had been stabbed by a knife. I was out of commission for running. Later at physio I learned that I had IT band syndrome. I immediately started rehab which consisted of strengthening my glutes and rolling out my IT band. It turned out that one specific exercise that focused on my gluteus medius was the main fix.&lt;/p>
&lt;p>Thankfully even with the injury I was still able to swim and bike mostly pain free so that training continued. Running was an issue though: I desperately needed to increase my running volume if I was going to do the Vancouver Marathon but even with my rehab going well I wasn&amp;rsquo;t able to add volume back at the pace I needed. I ultimately made the tough decision to withdraw from the marathon and transfer my registration to the 8 km race the same day.&lt;/p>
&lt;p>I had been planning to run the marathon with my brother-in-law Patrick and a number of his friends. We were all staying together and the energy was exciting. I was gutted that I wasn&amp;rsquo;t doing the marathon with everybody and was feeling down. In a pessimistic mood I decided that I was going to just treat the 8 km as a chill training run. One of Patrick&amp;rsquo;s friends Adam insisted that there was no way that this would happen and that once I heard the anthem I would full send it.&lt;/p>
&lt;p>Adam was right. As I lined up in the starting corral the anthem began to play and something came over me. I started at about a 5:10/km which was a minute faster than I was planning to run, but also felt comfortable. After the first two kilometers I just kept picking it up and throwing down negative splits. By the last kilometer I was moving at a good 4:30/km. The pace was challenging for me and my heart was red lining at the end. I remember thinking that the finish line was so far away! But, it was only an 8 km race so I was refreshed quite quickly after and ready to watch my friends complete the marathon. Given the circumstances I was very happy with my result.&lt;/p>
&lt;div class="callout">
&lt;p>&lt;strong>🏆 Vancouver 8 km Results — May 4, 2025 🏆&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>8 km run — 39:33 (chip time).&lt;/li>
&lt;/ul>
&lt;p>Finished 73/2042 overall and 5/79 in my age group (males 25-29).&lt;/p>
&lt;/div>
&lt;p>Shortly after Vancouver Patrick convinced me to do a beer mile with him and some of his friends. I was hesitant at first, but ultimately decided it&amp;rsquo;d be fun to try once. As a card carrying celiac I couldn&amp;rsquo;t drink beer and regardless I&amp;rsquo;m not much of a drinker. Instead I opted for Coca-Cola — special Quebec edition Coca-Cola with maple syrup. I would come to regret this decision.&lt;/p>
&lt;p>We started by chugging our first bottle. I quickly was reminded that Coca-Cola burns when you try to drink it fast. Not an issue for my fellow beer drinkers. Worse than the burn though was the sickly sweetness. My four opponents were fast drinkers and runners so right off the bat I was left in the dust by all but one. No worries though. I was just hoping to not come in last. The laps came and past quickly with each bottle of Coke getting harder to down. Much to my joy one of my opponents DNF&amp;rsquo;d on the first bottle so I was guaranteed at least second last. Towards the end I made a surprising leap to third last as another opponent DNF&amp;rsquo;d for puking. Not too shabby.&lt;/p>
&lt;p>Immediately following the race I was riding an insane sugar high. I was buzzing with energy while my fellow beer drinkers were down in the dumps. However, as my sugar high wore off and the alcohol set in the mood reversed pretty quickly. All things considered I would do it again.&lt;/p>
&lt;div class="callout">
&lt;p>&lt;strong>🏆 Beer Mile Results — May 18, 2025 🏆&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>1 mile run — 8:23 (chip time).&lt;/li>
&lt;/ul>
&lt;p>Finished 4/5 overall and 1/1 in my age group (males 25-29).&lt;/p>
&lt;/div>
&lt;p>The next race on the calendar was the Great White North Olympic Triathlon in July where I was hoping to beat my brother-in-law Patrick. In late June my coach Jason organized a three day open water swim camp that I attended with Patrick. Historically I&amp;rsquo;ve been terrified of open water swimming and mass starts so this was a great learning opportunity. The most memorable part was the repeated mass start practice we did where the coaches kept corralling us closer and closer together as we floated in pack formation in the middle of the lake. Then they&amp;rsquo;d shout go and we&amp;rsquo;d all start swimming like mad. There were people beside me, in front of me, over me, under me and everywhere else you could imagine. Limbs flailed around carelessly. It was terrifying. But, doing it so many times removed the terror from it. I learned to be okay with not having my own personal space for swimming. I also enjoyed learning to draft and the overall bump in my swim volume for that week.&lt;/p>
&lt;p>Eventually race day came. It was a rainy morning, but that cleared up and made for cool race conditions. In the warmup swim the water was pleasantly warm. Patrick and I did our pre-race prayer together and then got to the start line. The start was pretty hectic and I quickly got ahead of Patrick. Thanks to my open water practice I didn&amp;rsquo;t panic this time. Instead I kept calm by talking to myself and negotiating with my lizard brain that everything was okay. On the aussie exit coming into the second lap I got too excited and ran to fast. As I got back in the water I had to slow down a bit to catch my breath. Towards the end of the second lap I picked up my pace though. Overall I was quite happy with the swim.&lt;/p>
&lt;p>T1 went well and I enjoyed experiencing the ease of a trisuit for the first time. The bike was a pretty straightforward affair where I focused on hydrating and eating well. The first time I saw Patrick he was only a few minutes behind me which lit a fire under my butt. However, the next time I saw him he was tens of minutes behind me. At the time I thought he was just slow, but in fact he had to deal with a flat. Regardless, I knew I was basically guaranteed to win at this point.&lt;/p>
&lt;p>Out on the run I ran my fastest ever 10k while still feeling well within my limits. No visits to the pain cave were made. For a good chunk I was following directly on the heels of my now friend Kurt — I didn&amp;rsquo;t know him at the time. When he eventually blew up I powered through and sprinted to the finish line.&lt;/p>
&lt;div class="callout">
&lt;p>&lt;strong>🥉 Great White North Olympic Triathlon Results — July 6, 2025 🥉&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>1.5 km swim — 27:52 (watch time).&lt;/li>
&lt;li>T1 - 2:01 (watch time).&lt;/li>
&lt;li>50 km bike — 1:35:11 (watch time).&lt;/li>
&lt;li>T2 - 1:05 (watch time).&lt;/li>
&lt;li>10 km run — 49:36 (watch time).&lt;/li>
&lt;li>Overall time — 2:55:50 (chip time).&lt;/li>
&lt;/ul>
&lt;p>Finished 12/125 overall and 3/9 in my age group (males 21-29).&lt;/p>
&lt;/div>
&lt;p>The rest of the summer was filled with some fun B races. The first was the St. Albert Sprint Triathlon. In this race I pushed a lot closer to the edge than I had in other races. I was the first out of the water in my lane after a hard driving swim on a 2-stroke breathing pattern. On the bike I pushed harder than I ever had before passing lots of people. Although, once the faster racers in the following heats got on the course they quickly passed me. My intensity was high enough on the bike that I took in very little water and nutrition because my gut couldn&amp;rsquo;t tolerate it. Out on the run I learned I had overcooked the bike because I was dying to barely hold on to a sub 5 min/km pace. I couldn&amp;rsquo;t stop looking at my watch! The pacing wasn&amp;rsquo;t perfect, but the results were a noticeable improvement.&lt;/p>
&lt;div class="callout">
&lt;p>&lt;strong>🏆 St. Albert Sprint Triathlon Results — August 10, 2025 🏆&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>750 m swim — 15:19 (watch time).&lt;/li>
&lt;li>T1 - 1:30 (watch time).&lt;/li>
&lt;li>20 km bike — 37:21 (watch time).&lt;/li>
&lt;li>T2 - 1:06 (watch time).&lt;/li>
&lt;li>5 km run — 24:12 (watch time).&lt;/li>
&lt;li>Overall time — 1:19:46 (chip time).&lt;/li>
&lt;/ul>
&lt;p>Finished 26/130 overall and 4/7 in my age group (males 25-29).&lt;/p>
&lt;/div>
&lt;p>The second B race was the Do North Sprint Triathlon. This is one of my favorite sprint triathlon courses. My brother Jon was also racing in a different heat. In the swim I felt exceptionally exhausted but it turns out that was just because I was setting a new PB. I had forgotten to pre-untie my runners so that waylaid me in T2. Out on the run course I held on to a sub 5 min/km pace more manageably this time. Best of all a whole gaggle of people I knew were at the finish line were there to cheer me on. Having that many fans was a rare and special treat for me.&lt;/p>
&lt;div class="callout">
&lt;p>&lt;strong>🏆 Do North Sprint Triathlon Results — August 23, 2025 🏆&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>750 m swim — 14:17 (watch time).&lt;/li>
&lt;li>T1 - 2:39 (watch time).&lt;/li>
&lt;li>20 km bike — 35:28 (watch time).&lt;/li>
&lt;li>T2 - 2:17 (watch time).&lt;/li>
&lt;li>5 km run — 23:33 (watch time).&lt;/li>
&lt;li>Overall time — 1:18:21 (chip time).&lt;/li>
&lt;/ul>
&lt;p>Finished 12/28 in my age group (males 20-29).&lt;/p>
&lt;/div>
&lt;p>After my failed marathon attempt earlier in the season I was determined to make it happen within the year. I was looking for a race late enough in the year to give me enough time to build the running volume I needed. I was considering doing the Regina marathon but the timing was tight. I was also considering just running a marathon in the river valley by myself just to say I had done it. In a fortunate turn of events though I had learned from a fellow racer at the Great White North that the Victoria marathon still had some last minute openings so I pulled the trigger and signed up.&lt;/p>
&lt;p>Training for the marathon entered me into new territory for running volumes which was a little scary from an injury perspective. I missed the relative comfort of multi-modal triathlon training. Marathon training was just a lot of pounding pavement which was hard on my legs. Thankfully no injuries ruined my build though. I peaked at 70 km week, but realistically was holding something closer to 40 km a week for a sustained period of time. My peak weeks ended up being during some travel to Europe. For my biggest long run I had some niggles I was worried about so I opted to run it around a track I found in Paris to avoid any irregularities in the ground. It was raining that day too. Quite the test of my mental fortitude running 29 km around a track. I fondly remember this one French boy who would cheer me on each lap though!&lt;/p>
&lt;p>When the time came I flew to Victoria and was lucky enough to stay with some old friend who lived right near the start line. One of the friends Rebecca was also taking part in the half marathon. Unfortunately I had become really sick with a head cold the night before and the race and I was not sure how I would fair. Miraculously I woke up on race day feeling ok — as if my body sensed I just needed to feel good for the morning. The day ended up being on and off rainy which typically means I run faster because I don&amp;rsquo;t over heat.&lt;/p>
&lt;p>For the first few kilometers I felt nervous and sluggish, but I quickly settled into a very comfortable pace and felt great. I was following the 3:50 pacer and was about 100 meters behind them because it was so congested with runners. My baseline goal was to get sub four hours, but ambitiously I really wanted to beat my brother-in-law&amp;rsquo;s marathon PB which required a much more ambitious. I ended up running with a sticky note that told me the new target pace I would have to run to beat his PB depending on how late I started that pace. As I continued to feel good I decided that if I still felt fine at 20 km I would break off from the pacer group and go for it. Note that by that point I had started to run right by the pacer and he was amazing — really encouraging and talkative.&lt;/p>
&lt;p>As I had promised myself I was still feeling good at 20 km and I picked up my pace. In hindsight I didn&amp;rsquo;t pick up the pace as much as thought I did. I remember running around a square park and seeing the pacer group not that far behind me. Aerobically I continued to feel amazing, but at 38 km I discovered what a beast the marathon is and my legs started to catch up to me. They not only felt heavy but I slowly felt both my IT bands tightening up and seizing. Eventually I reached a point where I just physically couldn&amp;rsquo;t keep running. For the rest of the race I walk/ran in lots of pain.&lt;/p>
&lt;p>All the people I had passed started reeling in me. Then the pacer group passed me. I tried running with them for a bit but just couldn&amp;rsquo;t manage it. This was devastating because if I hadn&amp;rsquo;t been so ambitious I probably would have finished with them. It was around this point that a family friend was spectating and ran with me for a bit. That certainly urged me on and to be honest I was embarrassed to walk in front of them so I just kept running. On top of my legs completely falling apart my left shoulder was bothering me and had become extremely painful. In the last kilometer and fan chute all I could think about was crossing that finish line and stopping running.&lt;/p>
&lt;p>Once I crossed the finish line I was in shambles. My body was shivering from the rain, my legs and shoulder were destroyed, and I was definitely in a state of shock. I remember just lying down on the grass not knowing if I should be laughing or crying. It sounds silly but it was an amazing experience pushing both my body and mind so far.&lt;/p>
&lt;p>Post race my friends helped me shamble back to their place and then I spent the rest of the day sitting on the couch eating and watching the F1 movie. The next day I ended up on a redeye flight to Montreal which I can tell you is not ideal post-marathon recovery.&lt;/p>
&lt;div class="callout">
&lt;p>&lt;strong>🏆 Victoria Marathon Results — October 12, 2025 🏆&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>42.2 km — 3:54:23 (chip time).&lt;/li>
&lt;/ul>
&lt;p>Finished 1204/3220 overall and 208/433 in my age group (males 25-29).&lt;/p>
&lt;/div>
&lt;p>To cap off the year I ended up doing a week long indoor swim camp with my coach Jason. My technique improved massively during this concentrated effort.&lt;/p>
&lt;p>It was a big year and I&amp;rsquo;m really proud of everything I did. It was exhausting at times, but I grew so much and enjoyed racing and training with my brothers.&lt;/p></description></item><item><title>A Primer on Authorization</title><link>/blog/2025/a-primer-on-authorization/</link><pubDate>Wed, 19 Nov 2025 00:00:00 +0000</pubDate><guid>/blog/2025/a-primer-on-authorization/</guid><description>&lt;p>Software applications need to secure themselves by authenticating and authorizing requests that they receive. Authentication (authN) is the process of verifying a user&amp;rsquo;s identity — who they are. Authorization (authZ) is the process of verifying a user&amp;rsquo;s scope of access — what they can do. This blog post will help you build a mental model for how authorization is modeled and the spectrum of possible implementations.&lt;/p>
&lt;h2 id="authorization-models">Authorization Models&lt;/h2>
&lt;p>Fundamentally authorization is a question of the following form: Can &lt;code>subject&lt;/code> perform &lt;code>action&lt;/code> against &lt;code>object&lt;/code>. Here are some example subject-action-object &amp;ldquo;triples&amp;rdquo;.&lt;/p>
&lt;ul>
&lt;li>Can &lt;code>subject:user&lt;/code> perform &lt;code>action:edit&lt;/code> against &lt;code>object:document&lt;/code>?&lt;/li>
&lt;li>Can &lt;code>subject:service_account&lt;/code> perform &lt;code>action:delete&lt;/code> against &lt;code>object:database&lt;/code>?&lt;/li>
&lt;/ul>
&lt;p>You get the idea. A variety of authorization models exist which take different approaches to tracking these triples.&lt;/p>
&lt;h3 id="acls">ACLs&lt;/h3>
&lt;p>ACLs (access control lists) directly track each triple for every possible combination of access you need to provide. This is straight forward but gets out of hand quickly because it grows &lt;code>O(n^3)&lt;/code>.&lt;/p>
&lt;h3 id="rbac">RBAC&lt;/h3>
&lt;p>RBAC (role-based access control) introduces the concept of a role which adds indirection to the &lt;code>subject&lt;/code>. For example you could create a role called editor which has permissions to edit and delete documents. Then you could assign users to that role and they would transitively have those permissions. This drastically reduces the number of triples you need to manage. More advanced forms of RBAC introduce basic hierarchies or domains.&lt;/p>
&lt;h3 id="abac">ABAC&lt;/h3>
&lt;p>ABAC (attribute-based access control) takes a different approach and uses attributes of the subject, action, and/or object to control access. Rather than storing triples you define policies for the attributes you expect on a triple e.g. &lt;code>subject == object.owner&lt;/code>. This means you don&amp;rsquo;t need to store triples, but it means you need to send the relevant attributes every time you make a decision.&lt;/p>
&lt;h3 id="rebac">ReBAC&lt;/h3>
&lt;p>ReBAC (relationship-based access control) models relationships using a graph to answer complex transitive relationship. For example it could answer the question can &lt;code>subject:user&lt;/code> perform &lt;code>action:edit&lt;/code> against &lt;code>object:document&lt;/code> by tracing that the user is a member of a team which is a part of an organization which has access to edit the document. This provides indirection similar to RBAC, but to arbitrary levels of depth.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;h2 id="authorization-implementations">Authorization Implementations&lt;/h2>
&lt;p>Once you&amp;rsquo;ve chosen an authorization model that fits your use case you need an implementation of it. This can cover the spectrum from hand rolling it to going full zanzibar&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>. Implementations vary on a number of aspects:&lt;/p>
&lt;ul>
&lt;li>Authorization models supported.&lt;/li>
&lt;li>Complexity.&lt;/li>
&lt;li>Scalability.&lt;/li>
&lt;/ul>
&lt;img src="/authz-spectrum.png" width="500px">
&lt;p>The input to an authorization implementation is subject-action-object triple and the output is an allow or deny decision.&lt;/p>
&lt;img src="/authz-impl.png" width="400px">
&lt;p>This input and output is informed by an implementation&amp;rsquo;s schema and data. The schema is dependent on your authorization model. In RBAC the schema is the roles you define; in ABAC it is the attributes you want to match on; in ReBAC it is the relationships between different entities; ACLs are effectively schemaless. The data is the actual triples that you&amp;rsquo;re storing. Typically you would create this data when other events happen in your application. For example if a new user is created in your application in addition to creating that user in the application database you would create all the necessary triples to represent that users scope of authorization.&lt;/p>
&lt;h3 id="hand-rolled">Hand Rolled&lt;/h3>
&lt;p>Lots of projects start with a hand rolled authorization setup. Typically the schema is hardcoded and the data lives in the application database.&lt;/p>
&lt;p>&lt;strong>Authorization models supported:&lt;/strong> It&amp;rsquo;s easy enough to hand roll basic ACLs, RBAC, or even ABAC. But, the devil is in the details and there are subtle things you can get wrong. Don&amp;rsquo;t expect advanced RBAC or ReBAC.&lt;/p>
&lt;p>&lt;strong>Complexity:&lt;/strong> There is limited operational overhead because it lives in your application and uses your application database (you can use transactions). Expect technical debt from maintaining the authorization logic yourself though.&lt;/p>
&lt;p>&lt;strong>Scalability:&lt;/strong> Scales as far as your application will scale — which granted is probably pretty far. However, if you have other micro-services or clients that need to make authorization decisions they&amp;rsquo;ll need to call home to your application which is a scaling bottle neck.&lt;/p>
&lt;p>&lt;strong>Use this if:&lt;/strong> You probably just shouldn&amp;rsquo;t hand roll your own authorization code. Focus on what &lt;a href="https://podup.substack.com/p/jeff-bezoss-beer-tasting-analogy">makes your beer taste better&lt;/a>.&lt;/p>
&lt;h3 id="casbin">Casbin&lt;/h3>
&lt;p>An authorization framework allows you to avoid hand rolling the implementation while still embedding the authorization decisions in your application. &lt;a href="https://casbin.org/">Casbin&lt;/a> is an example of this kind of framework. You write the schema of your authorization model in a config file that Casbin loads at startup. The data lives in the application database.&lt;/p>
&lt;p>&lt;strong>Authorization models supported:&lt;/strong> Casbin supports ACLs, RBAC, ABAC, ReBAC, and a &lt;a href="https://casbin.org/docs/supported-models">lot more&lt;/a>. However, it&amp;rsquo;s ReBAC support is more primitive than some of the other offerings on the spectrum.&lt;/p>
&lt;p>&lt;strong>Complexity:&lt;/strong> Operational costs are very low. You write your schema in an existing DSL and your data lives in the application database (you can use transactions).&lt;/p>
&lt;p>&lt;strong>Scalability:&lt;/strong> Same scaling as hand rolled which is to say as far as you can scale a single application. If you have other micro-services or clients that need to make authorization decisions they&amp;rsquo;ll need to call home to your application which is a scaling bottle neck. It&amp;rsquo;s worth noting that Casbin does support coordinating between multiple replicas of a single application.&lt;/p>
&lt;p>&lt;strong>Use this if:&lt;/strong> This should be your default implementation. Reach for more advanced implementations when you have a specific need.&lt;/p>
&lt;h3 id="openfga">OpenFGA&lt;/h3>
&lt;p>The next step up is to run a dedicated authorization service outside of your application. OpenFGA is an example of such a service. These types of services are typically based on the Google &lt;a href="https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/">Zanzibar paper&lt;/a>. OpenFGA is a moderately faithful re-implementation of Zanzibar. Again you write the schema of your authorization model in a config file that OpenFGA loads at startup. However, now the data lives in a separate database from your application database.&lt;/p>
&lt;p>Now that we&amp;rsquo;re trying to atomically write to our application database and the authorization database we have a &lt;a href="https://authzed.com/blog/the-dual-write-problem">dual-write problem&lt;/a>. Give the linked article a read for a good description of the possible solutions.&lt;/p>
&lt;p>It&amp;rsquo;s worth noting that OpenFGA can run embedded in your application as a framework, but this defeats most of its benefits.&lt;/p>
&lt;p>&lt;strong>Authorization models supported:&lt;/strong> Everything.&lt;/p>
&lt;p>&lt;strong>Complexity:&lt;/strong> You now need to maintain a separate service and you have to contend with the dual-write problem.&lt;/p>
&lt;p>&lt;strong>Scalability:&lt;/strong> Any micro-services or clients doing authorization talk to OpenFGA which can scale independently of your application.&lt;/p>
&lt;p>&lt;strong>Use this if:&lt;/strong> You think you&amp;rsquo;ll have lots of micro-services/clients that need to make authorization decisions or you&amp;rsquo;re worried about scaling.&lt;/p>
&lt;h3 id="open-policy-agent">Open Policy Agent&lt;/h3>
&lt;p>Open Policy Agent takes a similar place to OpenFGA in the spectrum of implementations. However, OPA is tailored for ABAC and expects that all relevant data for an authorization decision is sent along with the input. It does have support for storing data, but only by asynchronously long polling your application for this data.&lt;/p>
&lt;p>&lt;strong>Authorization models supported:&lt;/strong> Primarily ABAC and RBAC.&lt;/p>
&lt;p>&lt;strong>Complexity:&lt;/strong> You now need to maintain a separate service and potentially have to manage long polling for data.&lt;/p>
&lt;p>&lt;strong>Scalability:&lt;/strong> Any micro-services or clients doing authorization talk to OPA which can scale independently of your application.&lt;/p>
&lt;p>&lt;strong>Use this if:&lt;/strong> You primarily care about ABAC and need some scaling.&lt;/p>
&lt;h3 id="spicedb">SpiceDB&lt;/h3>
&lt;p>SpiceDB is our final stop and is the most faithful open source Zanzibar re-implementation&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. Similar to OpenFGA you have the dual-write problem. SpiceDB offers two improvements over OpenFGA: First, it protects against the &lt;a href="https://authzed.com/docs/spicedb/concepts/zanzibar#new-enemy-problem">new enemy problem&lt;/a> with &lt;a href="https://authzed.com/docs/spicedb/concepts/consistency#zedtokens">ZedTokens&lt;/a> (zookies). Second, it supports multiple region deployments when backed by something like CockroachDB.&lt;/p>
&lt;p>&lt;strong>Authorization models supported:&lt;/strong> Everything.&lt;/p>
&lt;p>&lt;strong>Complexity:&lt;/strong> You need to to maintain a separate service and globally distributed database, properly use ZedTokens, and contend with the dual-write problem.&lt;/p>
&lt;p>&lt;strong>Scalability:&lt;/strong> This is about as good as it gets. SpiceDB copied a lot of the &lt;a href="https://authzed.com/docs/spicedb/ops/performance">performance&lt;/a> improvements from the original Zanzibar paper.&lt;/p>
&lt;p>&lt;strong>Use this if:&lt;/strong> You need global scaling and perfect consistency with ZedTokens.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>ReBAC is capable of modelling most other authorization models.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>I&amp;rsquo;ll be describing the spectrum of implementations with descriptions of specific projects. These projects are illustrative of a part of the spectrum, but are not the only project that exists on that part of the spectrum. I chose these projects through a mix of popularity, familiarity, and uniqueness.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>There are a number of Zanzibar-a-likes out there &lt;a href="https://authzed.com/blog/zanzibar-implementations">https://authzed.com/blog/zanzibar-implementations&lt;/a>.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Year in Review: 2024</title><link>/blog/2024/year-in-review-2024/</link><pubDate>Tue, 31 Dec 2024 00:00:00 +0000</pubDate><guid>/blog/2024/year-in-review-2024/</guid><description>&lt;p>For the past four years, I&amp;rsquo;ve been writing a year-in-review post (&lt;a href="/blog/2020/year-in-review-2020">2020&lt;/a>, &lt;a href="/blog/2021/year-in-review-2021">2021&lt;/a>, &lt;a href="/blog/2022/year-in-review-2022">2022&lt;/a>, &lt;a href="/blog/2023/year-in-review-2023">2023&lt;/a>). Writing these has been a wonderful exercise in reflection and gratitude. I&amp;rsquo;m excited to continue the tradition with this post.&lt;/p>
&lt;h2 id="top-10-experiences">Top 10 Experiences&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Getting baptized&lt;/strong> — In 2023, I decided to follow Jesus. This year, I decided to get baptized to publicly declare my faith in Christ.&lt;/li>
&lt;li>&lt;strong>Trip to Germany&lt;/strong> — Fermyon had an offsite in Berlin during the summer. Before the offsite, I spent a week traveling around Germany. The highlights were spending time in Heidelberg with a co-worker and exploring the city of Hamburg.&lt;/li>
&lt;li>&lt;strong>My first triathlon and half-marathon&lt;/strong> — On a whim, I developed an interest in racing triathlons this year. I raced my &lt;a href="/blog/2024/great-white-north-sprint-triathlon-race-report">first&lt;/a> ever sprint triathlon in July and followed it with &lt;a href="/blog/2024/do-north-sprint-triathlon-race-report">another one&lt;/a> in August. Later, in September, I ran my &lt;a href="/blog/2024/run-the-river-half-marathon-race-report">first&lt;/a> half-marathon. It turns out I really enjoy racing!&lt;/li>
&lt;li>&lt;strong>Learning to play piano&lt;/strong> — In January, I began piano lessons with a local teacher. I&amp;rsquo;ve been very committed to my practice and have seen great improvement. It was fun to cap off the year with a small piano recital.&lt;/li>
&lt;li>&lt;strong>Buying a house&lt;/strong> — I purchased a home in West Edmonton!&lt;/li>
&lt;li>&lt;strong>Trip to Salt Lake City&lt;/strong> — I attended KubeCon NA in Salt Lake City with Fermyon this November. Afterwards, I spent a few extra days in town. I enjoyed my first-ever ballet, ate delicious food, watched an NHL game, and did my first mini-solitude retreat.&lt;/li>
&lt;li>&lt;strong>Trip to Minneapolis&lt;/strong> — For American Thanksgiving, I went to Minneapolis with my parents to visit family. I had a great time fostering adult relationships with cousins I hadn&amp;rsquo;t seen in a decade.&lt;/li>
&lt;li>&lt;strong>Knife-making weekend&lt;/strong> — As a special gift, my dad took all of us sons on a knife-making weekend. Over three days, we crafted chef&amp;rsquo;s knives by hand. It was extremely cool to learn the process and a great bonding experience.&lt;/li>
&lt;li>&lt;strong>Biking 200 km&lt;/strong> — For years, I&amp;rsquo;ve aspired to bike 100 miles (161 km) in a day. I finally trained enough this year and accomplished it late in the fall with my brother-in-law and dad. Our original goal was 180 km (the Ironman bike distance), but I felt so energized that I pushed through to 200 km.&lt;/li>
&lt;li>&lt;strong>Trip to Mexico&lt;/strong> — I spent a week in Cancun for a Fermyon offsite and extended the trip with a week in Puerto Vallarta with family. It was a wonderful break, and I cherished the family time.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to my &lt;strong>Austin trip&lt;/strong>, &lt;strong>Pasadena trip&lt;/strong>, &lt;strong>Seattle trip&lt;/strong>, &lt;strong>the Oilers playoff run&lt;/strong>, &lt;strong>playing recreational hockey&lt;/strong>, &lt;strong>getting involved at Celebration&lt;/strong>, and &lt;strong>the family vacation at Hubbles Lake&lt;/strong>.&lt;/p>
&lt;h2 id="top-10-movies-and-tv-shows">Top 10 Movies and TV Shows&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Masters of the Air&lt;/strong> — Band of Brothers and The Pacific are some of my favorite shows of all time — in fact, I re-watched them this year. A third addition to this family of shows was a dream come true. The series really drove home how terrifying air combat in WW2 was.&lt;/li>
&lt;li>&lt;strong>30 Rock&lt;/strong> — I slept on this show for too long. In my opinion, it keeps up with the greats like The Office or Community.&lt;/li>
&lt;li>&lt;strong>Society of the Snow&lt;/strong> — An incredible true story. This film stretched my notion of what humans can survive.&lt;/li>
&lt;li>&lt;strong>The Ministry of Ungentlemanly Warfare&lt;/strong> — One of the best action flicks I&amp;rsquo;ve seen in a long time, and it&amp;rsquo;s a true story to boot.&lt;/li>
&lt;li>&lt;strong>Slow Horses&lt;/strong> — A delightful twist on your usual spy series. Great comedy, tension, and character development.&lt;/li>
&lt;li>&lt;strong>We Live in Time&lt;/strong> — I thought the storytelling in this romance was great, and the acting was phenomenal.&lt;/li>
&lt;li>&lt;strong>The Zone of Interest&lt;/strong> — A deeply haunting recounting of the Holocaust from the German perspective.&lt;/li>
&lt;li>&lt;strong>Invincible&lt;/strong> — I&amp;rsquo;m not typically into the superhero genre, but Invincible is great. I love all the side characters like Allen the Alien.&lt;/li>
&lt;li>&lt;strong>The Rehearsal&lt;/strong> — Nathan Fielder is wild. No spoilers, just go watch this and have your brain melted.&lt;/li>
&lt;li>&lt;strong>Blackberry&lt;/strong> — It was fun to learn about more Canadian tech history.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to &lt;strong>WeCrashed&lt;/strong>, &lt;strong>Bullet Train&lt;/strong>, &lt;strong>Jerry and Marge Go Large&lt;/strong>, &lt;strong>American Fiction&lt;/strong>, &lt;strong>Fall Guy&lt;/strong>, and &lt;strong>Dune 1 &amp;amp; 2&lt;/strong>.&lt;/p>
&lt;h2 id="top-10-books">Top 10 Books&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Into Thin Air by Jon Krakauer&lt;/strong> — A gripping tale of an Everest summit gone wrong. I need to read more narrative non-fiction like this.&lt;/li>
&lt;li>&lt;strong>Remarkably Bright Creatures by Shelby Van Pelt&lt;/strong> — This book contains a story that you can&amp;rsquo;t help but fall in love with.&lt;/li>
&lt;li>&lt;strong>Pirate Hunters by Robert Kurson&lt;/strong> — Not his best title, but Robert Kurson doesn&amp;rsquo;t disappoint.&lt;/li>
&lt;li>&lt;strong>Elon Musk by Walter Isaacson&lt;/strong> — The older I get, the less I find I want to replicate Elon Musk. Regardless, it&amp;rsquo;s a fascinating and inspiring story to learn from.&lt;/li>
&lt;li>&lt;strong>Blue Like Jazz by Donald Miller&lt;/strong> — This was a meaningful read based on where I was in my faith journey.&lt;/li>
&lt;li>&lt;strong>Factfulness by Hans Rosling&lt;/strong> — I learned that my intuition about the world is very wrong.&lt;/li>
&lt;li>&lt;strong>What Every Real Estate Investor Needs to Know About Cash Flow by Frank Gallinelli&lt;/strong> — This helped ground me in the terminology and math of real estate investing as I was considering purchasing a home.&lt;/li>
&lt;li>&lt;strong>A Concise Guide to Macroeconomics by David A. Moss&lt;/strong> — I still don&amp;rsquo;t feel like I fully understand macroeconomics at an intuitive level, but I definitely learned something.&lt;/li>
&lt;li>&lt;strong>Die With Zero by Bill Perkins&lt;/strong> — Truthfully, I wasn&amp;rsquo;t the biggest fan of the writing style, but I love the idea and have recommended it to many people.&lt;/li>
&lt;li>&lt;strong>In the Realm of Hungry Ghosts by Gabor Maté&lt;/strong> — This changed my perspective on addiction.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to &lt;strong>The Problem of Jesus by Mark Clark&lt;/strong>, &lt;strong>A Good Girl&amp;rsquo;s Guide to Murder by Holly Jackson&lt;/strong>, &lt;strong>Good Girl, Bad Blood by Holly Jackson&lt;/strong>, &lt;strong>Cibola Burn by James S.A. Corey&lt;/strong>, &lt;strong>Practicing the Way by Jon Mark Comer&lt;/strong>, and &lt;strong>The Three-Body Problem by Cixin Liu&lt;/strong>.&lt;/p>
&lt;h2 id="top-10-others">Top 10 Others&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>For Cryin&amp;rsquo; Out Loud! by Finneas&lt;/strong> &amp;mdash; This year I discovered Finneas as a solo artist, and I fell in love with his music. He&amp;rsquo;s a big inspiration for the music I write.&lt;/li>
&lt;li>&lt;strong>Tmux&lt;/strong> &amp;mdash; I finally bit the bullet and learned Tmux. Now it&amp;rsquo;s a regular part of my development flow, and I don&amp;rsquo;t know how I lived without it.&lt;/li>
&lt;li>&lt;strong>Terraforming Mars&lt;/strong> &amp;mdash; Thanks to my friend who let me borrow this. I jammed a lot of Terraforming Mars this year.&lt;/li>
&lt;li>&lt;strong>Logic Pro&lt;/strong> &amp;mdash; I decided to upgrade from GarageBand to Logic Pro, and it has been so worth it. Logic Pro is a phenomenal piece of software.&lt;/li>
&lt;li>&lt;strong>Cortex Podcast&lt;/strong> &amp;mdash; I don&amp;rsquo;t even care about half the things they talk about, but I just love hearing them talk.&lt;/li>
&lt;li>&lt;strong>Epix Pro Gen 2&lt;/strong> &amp;mdash; This is definitely overkill for me, but it has been so nice to be able to track activities without my phone now. The flashlight is really handy too.&lt;/li>
&lt;li>&lt;strong>Lucky Paper Podcast&lt;/strong> &amp;mdash; I&amp;rsquo;m less focused on cube and MTG this year, but even still, I enjoy the content Andy and Anthony put out.&lt;/li>
&lt;li>&lt;strong>SimCity 4&lt;/strong> &amp;mdash; This was a nostalgia download while I was in Mexico with my family. Building cities together with Jon was such a throwback.&lt;/li>
&lt;li>&lt;strong>First of October&lt;/strong> &amp;mdash; This once-a-year band is also another big inspiration for the music I write.&lt;/li>
&lt;li>&lt;strong>Wilson by Ashe&lt;/strong> &amp;mdash; Ashe released a banger of an album this year. &amp;ldquo;Please Don&amp;rsquo;t Fall in Love With Me&amp;rdquo; is my favorite song on the album.&lt;/li>
&lt;/ol>
&lt;h2 id="reflecting-on-2024">Reflecting on 2024&lt;/h2>
&lt;h3 id="career">Career&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;m very happy at Fermyon, so I want to focus on keeping a good thing good. The biggest priority here is keeping a healthy work-life balance so I don&amp;rsquo;t get burnt out like I did in university. I also want to up my game on the 1-1 front. Last year, I did a good job of doing 1-1&amp;rsquo;s with everyone on the team, but then I quickly lost steam. I want to build more regular 1-1&amp;rsquo;s into my schedule because improving relationships will help me do better work at Fermyon and open up job opportunities in the future. Not to mention the benefit of socializing in an often isolating remote job. Finally, I want to learn to love the problems we&amp;rsquo;re solving for customers and not just the solutions we&amp;rsquo;ve created. Put another way, I want to focus on writing high-impact code that matters.&lt;/p>&lt;/blockquote>
&lt;p>It&amp;rsquo;s hard to believe that I&amp;rsquo;ve been out of university and working full-time for two years now. I continue to be happy with how it is going. I&amp;rsquo;m happy with my current work-life balance; I did a better job with scheduling regular 1-1&amp;rsquo;s; I gave my intro to WebAssembly talk at a conference in Austin and ran a workshop in Pasadena; and I became the champion for the &lt;a href="https://github.com/WebAssembly/wasi-observe">WASI Observe&lt;/a> effort.&lt;/p>
&lt;p>I made a small amount of headway on local networking — I met a few engineers in the local scene — but I haven&amp;rsquo;t done as much to nurture this as I wanted to. I also completely dropped the ball on trying a co-working space. Overall, I&amp;rsquo;m gainfully employed and happy with the way things are trending.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Maintain a sustainable work-life balance.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Build a more rigorous schedule of 1-1&amp;rsquo;s for learning, mentoring, and socializing.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Give another conference talk.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Start building a professional network in Edmonton.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Try out a co-working space.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Find a way to contribute to the upstream Wasm ecosystem.&lt;/li>
&lt;/ul>
&lt;h3 id="finances">Finances&lt;/h3>
&lt;blockquote>
&lt;p>Last year was a financial success, so I plan to keep it simple and stay the course. To make sure I hit my saving/donating goals, I&amp;rsquo;m going to set up automatic deposits. I also want to spend some time at the start of the year tweaking how I&amp;rsquo;m using YNAB to be more effective. As a stretch goal, it&amp;rsquo;d be good to find a financial advisor who can help me with my complex situation of dual citizenship. Also, depending on how things go throughout the year, I might look into buying a home with a rental basement suite.&lt;/p>&lt;/blockquote>
&lt;p>Keeping it simple was a good plan and worked well for me. As per usual, budgeting with YNAB has been instrumental to my success. YNAB helps keep me on track at a micro level by answering questions like &amp;ldquo;Can I afford to eat out again this month?&amp;rdquo; and at a macro level with info like my net worth and how quickly it is increasing.&lt;/p>
&lt;p>Throughout the year, I saved 25.3% of my gross income (37.0% net income) and donated 10.1% of my gross income (14.7% net income). The importance and power of &amp;ldquo;paying yourself first&amp;rdquo; via automatic transfers cannot be overstated. I view these saving and donating goals as fairly aggressive, and the more I can automate them, the less willpower I require of myself.&lt;/p>
&lt;p>My current asset allocation is as follows: 45.4% equities, 7.6% bonds, 8.0% crypto, 13.4% real estate, and 25.6% cash. A couple of notes: this doesn&amp;rsquo;t represent my target allocation (which is quite different); it represents my actual allocation after any portfolio drift occurs. It&amp;rsquo;s difficult to accurately assess your current equity in a property, so I aimed conservatively 🤷. Cash represents my emergency fund and anything I&amp;rsquo;m holding liquid to later move into another asset class — it does not include money assigned to my regular budget.&lt;/p>
&lt;p>I did end up looking for a financial advisor and having a few conversations. The outcome of the search was that I determined I don&amp;rsquo;t yet need a financial advisor. A good piece of advice for future Caleb is that when I do need a financial advisor, I should make sure to find one that is paid on a flat rate and is a fiduciary.&lt;/p>
&lt;p>Perhaps the biggest news in this category is that I ended up purchasing my first home! It&amp;rsquo;s a bungalow in West Edmonton, and it has a legal basement suite that I rent out. I didn&amp;rsquo;t have tons of time to save for the down payment, so I only put 5% down on a 5-year fixed mortgage. This means that, on average, the house is not cash flowing — we&amp;rsquo;re in the red. I&amp;rsquo;m okay with this situation for four reasons. First, I have large emergency funds (~5 months), so I&amp;rsquo;m not concerned about covering this deficit. Second, I&amp;rsquo;m very bullish on the neighborhood it&amp;rsquo;s in and the potential appreciation. Third, I have very long time horizons, so I expect rent to appreciate enough that it will start cash flowing. Fourth, independent of the financials, I like the home and would have made the decision on that basis.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Use &lt;a href="https://ynab.com">YNAB&lt;/a> all year.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Save 20% of my gross income.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Donate 10% of my gross income.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Find a financial advisor.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Buy a home.&lt;/li>
&lt;/ul>
&lt;h3 id="physical-health">Physical Health&lt;/h3>
&lt;blockquote>
&lt;p>Most of my focus at the start of the year will be around rehabbing my knee. In terms of exercise, I&amp;rsquo;m setting a more realistic goal of twice a week at a minimum. I&amp;rsquo;m also setting the goal of being able to do a pull-up because I&amp;rsquo;ve always wanted to be able to do one.&lt;/p>&lt;/blockquote>
&lt;p>If I could have peered into the future for one answer last year, it would have been to ask how my knee is doing. Great! In fact, I rehabbed it so well that I can&amp;rsquo;t even remember which knee I injured. It turns out that in addition to a torn MCL, I also had a bad bone bruise, which is why the recovery was taking longer than expected. In even more good news, I&amp;rsquo;ve made it through 2024 without any additional serious injuries.&lt;/p>
&lt;p>My year was filled with lots of fun team sports. I played on one and a half hockey teams, and my skill development has been notable. I&amp;rsquo;m starting to feel like I need to graduate out of the original team I played on so that I can learn more at higher levels. I also played multi-sport this year, which was nice because it felt like I was in high school gym again (which I loved).&lt;/p>
&lt;p>The biggest twist was that I got into racing triathlons. This was an unexpected but pleasant surprise. I love training for triathlons, the race day is fun, and it&amp;rsquo;s a great mix of sports I enjoy. Due to issues with my knees in years past, I had sort of just assumed that I would never run again. So it felt great to break that barrier with the help of triathlons and ultimately run an entire half-marathon! In December, I found a great triathlon coach to help me with training plans for my races next year. I&amp;rsquo;m excited to see the difference a coach makes.&lt;/p>
&lt;p>Inspired by getting into triathlons, I&amp;rsquo;ve made good progress on checking off the parts of an Ironman individually. I biked more than the 180 km of an Ironman, and I did a 4 km swim. Now I just need to do a marathon. I figure if I can do the parts of an Ironman by themselves, it&amp;rsquo;s not that far-fetched to string them together.&lt;/p>
&lt;p>The only thing I missed this year was doing a pull-up by the end of the year. Frankly, I just started working on this goal too late in the year. With more time, I think I could have done it. Regardless, I made good progress and have laid a strong foundation for hitting this goal next year. I&amp;rsquo;ve also found my way back to StrongLifts 5x5 and have been feeling stronger than ever before.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Rehab my knee injury.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Be able to do a pull-up by the end of the year.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Do yoga every day in January.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Do a meaningful workout at least twice a week (yoga at studio/strength training/go for a run or bike/etc.).&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Play a sport once a week.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Bike an imperial century.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Train for a try-a-triathlon.&lt;/li>
&lt;/ul>
&lt;h3 id="mental-health">Mental Health&lt;/h3>
&lt;blockquote>
&lt;p>My main goal for mental health in this coming year is to build a more healthy relationship with my phone and social media. I don&amp;rsquo;t think the goals I&amp;rsquo;ve listed are sufficient for a perfectly healthy relationship, but I do think they are achievable and a good starting point.&lt;/p>&lt;/blockquote>
&lt;p>Same old, same old here. The macro of my mental health is 👍. But the micro is all flubs. I&amp;rsquo;m probably setting unrealistic goals — or maybe phone boundaries are just really hard? It seems like my mental health is mostly downstream of other things like digital life, faith, physical health, relationships, etc. Maybe I need to rethink this category.&lt;/p>
&lt;p>I did start two habits that have helped a bit. First, I took my phone charger out of my bedroom and placed it in a room across the house. Now I wake up to a digital alarm clock. This has helped me to avoid doom scrolling right before bed and also means that I&amp;rsquo;ll typically wait a few hours in the morning before I go find my phone.&lt;/p>
&lt;p>Second, I started using app blockers. On my work laptop, I have schedules that automatically block things like YouTube during the workday. On my personal phone, I occasionally flip it onto 24 hours of strict mode, preventing me from using YouTube. I&amp;rsquo;m not that rigorous about it yet, so these tricks only work when I&amp;rsquo;m feeling at least moderately disciplined.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Pick a few Sundays to stay off my phone.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Don&amp;rsquo;t use my phone between 9pm and 9am.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Have a &lt;a href="https://allenpike.com/2023/have-a-research-question">research question&lt;/a> about how productive people relate to their phones and social media.&lt;/li>
&lt;/ul>
&lt;h3 id="relationships">Relationships&lt;/h3>
&lt;blockquote>
&lt;p>My relationship goals this year are mostly focused on maintenance with some opportunities on the side to grow my circle of relationships.&lt;/p>&lt;/blockquote>
&lt;p>The most notable thing here is that I&amp;rsquo;ve found myself a fantastic community at Celebration church. This has been a big improvement to my quality of life.&lt;/p>
&lt;p>I&amp;rsquo;m being relatively strict with marking regular lunches, dinner parties, and staying in touch with friends abroad as failed. I did all of those things, but just not quite to the level of regularity and discipline that I was hoping to do them with.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Prioritize and maintain relationships with my family and close friends.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Schedule regular lunches with friends and family in town.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Host six dinner parties.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep in touch with friends abroad.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Meet more local people in the music/tech/Christian scenes.&lt;/li>
&lt;/ul>
&lt;h3 id="faith">Faith&lt;/h3>
&lt;blockquote>
&lt;p>Coming to faith last year was a big change. I want to focus on approaching it with intentionality in 2024. Finding a community to get plugged into is going to be critical.&lt;/p>&lt;/blockquote>
&lt;p>This year I ended up making Celebration my home church, and I&amp;rsquo;ve been feeling very connected. I&amp;rsquo;ve joined a small group and started volunteering to help lead their youth program. I&amp;rsquo;ve made lots of friends through this. Oh, and I got baptized!&lt;/p>
&lt;p>I also grew in my spiritual practices this year. Towards the end of the year, I had a habit of reading scripture in the morning before breakfast 2-4 times a week. I only made it through 4 books of the Bible, and I definitely dropped the ball on sabbathing.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Find a church home.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Get plugged into a small group at Church.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Build a habit of reading the Bible and praying three mornings a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Read 8 books of the Bible.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Pick a few Sundays to try sabbathing.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure">Travel and Adventure&lt;/h3>
&lt;blockquote>
&lt;p>For 2024 I&amp;rsquo;m going to list a bunch of things that I potentially could do, but go in with my eyes open and make them all stretch goals. Hopefully, this list will give me inspiration throughout the year, but will also give me the space to be spontaneous.&lt;/p>&lt;/blockquote>
&lt;p>I didn&amp;rsquo;t specifically do any of the stretch goals this year, but I&amp;rsquo;m still pleased with the amount of travel and adventure I got up to this year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Family canoeing trip in Bowron Lake Provincial Park.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Go to New York.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take dance lessons.&lt;/li>
&lt;/ul>
&lt;h3 id="organization">Organization&lt;/h3>
&lt;blockquote>
&lt;p>I felt like I needed a new section to track my progress around productivity, organization, and digital tools. I really want to find a note-taking tool I can consolidate on, like I did for task management with Todoist. Right now, I just use a mix of random tools that aren&amp;rsquo;t working well for me.&lt;/p>&lt;/blockquote>
&lt;p>Todoist continues to be useful for me. I&amp;rsquo;m not super rigorous with it. You won&amp;rsquo;t find me reviewing it every day to make sure it is up to date. I&amp;rsquo;m also not entering every single task I have into it. But it is still valuable for four reasons. First, it helps me to easily track recurring tasks I need to deal with (e.g., check the furnace filter). Second, it lets me forget about things that are really far away (e.g., renew my driver&amp;rsquo;s license). Third, it helps me consolidate my task anxiety into one place — no more sticky notes, whiteboard scrawlings, etc. Fourth, it allows me to write down aspirational things that I&amp;rsquo;m unlikely to do but take up mind space (e.g., sort my filed papers).&lt;/p>
&lt;p>I did start using Obsidian this year — I&amp;rsquo;m using approximately 2% of its functionality — but I&amp;rsquo;m using it. I use it to store notes from important conversations and other important lists I want to track. Google Keep acts as my dumping ground where things go first.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep using Todoist for task management.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Consolidate on a note-taking tool like Obsidian.&lt;/li>
&lt;/ul>
&lt;h3 id="building">Building&lt;/h3>
&lt;blockquote>
&lt;p>Building things is still exciting to me and, perhaps more importantly, is a compounding activity, so I&amp;rsquo;m keeping this goal around in a very simple form. I would hope that the project either is deeply technical or solves a problem. In the case of a deeply technical project, it will act as a learning/teaching opportunity. If it solves a problem, then it will help me flex my entrepreneurial muscles. I&amp;rsquo;ve moved the hackathon to a stretch goal because I still think it is a great idea, but it would take some meaningful work.&lt;/p>&lt;/blockquote>
&lt;p>I didn&amp;rsquo;t do anything here. I was too busy with my other goals. One thing worth noting is that I still get emails asking for support for &lt;a href="https://mixtapemanager.ca/">MixtapeManager&lt;/a>. I&amp;rsquo;m not sure what to do about this. There doesn&amp;rsquo;t seem to be enough momentum with it to invest more effort into it. But there is enough traction that it feels weird to kill it.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build, publish, and write about one meaningful project.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Organize a low-key weekend-long remote hackathon with my friends.&lt;/li>
&lt;/ul>
&lt;h3 id="reading">Reading&lt;/h3>
&lt;blockquote>
&lt;p>Based on how little I read last year, I&amp;rsquo;m toning this goal back a little bit to make it more reasonable. One potential issue that occurs to me is that if I only read 12 books, that&amp;rsquo;s still not a lot to construct a top 10 from. That can be next year&amp;rsquo;s problem.&lt;/p>&lt;/blockquote>
&lt;p>I knocked this one out of the park. This year, I read 15 books.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Read 1+ books a month (for a total of 12+ over the year).&lt;/li>
&lt;/ul>
&lt;h3 id="writing">Writing&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;m going to keep this goal the same as last year. It&amp;rsquo;s worth noting that ideally I publish about once a month to practice my consistency.&lt;/p>&lt;/blockquote>
&lt;p>Including this post, I wrote nine blog posts this year. Four of them were personal, two were reflection pieces, two were about productivity, and one was a technical tutorial. I was most proud of my blog post on &lt;a href="https://calebschoepp.com/blog/2024/four-remote-communication-anti-patterns-to-avoid/">remote communication&lt;/a>.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 12+ blog posts.&lt;/li>
&lt;/ul>
&lt;h3 id="music">Music&lt;/h3>
&lt;blockquote>
&lt;p>My most extensive list of goals for 2024 is here in the music section. I just have so much that I&amp;rsquo;m excited to do. The headline here is that I&amp;rsquo;m starting to take piano lessons. I&amp;rsquo;m stoked to get some formal training and plug the theory and ear training gaps that I have. My hope is that while I devote a lot of my energy to piano I&amp;rsquo;ll still have enough to make a modicum of progress on guitar. We&amp;rsquo;ll see how that goes.&lt;/p>&lt;/blockquote>
&lt;p>Regularly incorporating playing and writing music into my life has been one of the best decisions I&amp;rsquo;ve made in my adult life. In January, I started taking piano lessons, and I&amp;rsquo;ve made a ton of progress. As it turns out, practicing 30 minutes every day makes a difference. I ended the year with a piano recital, and in January, I will be taking my level 1 Royal Conservatory exam.&lt;/p>
&lt;p>Things were more stagnant on the guitar front this year. I didn&amp;rsquo;t really make any progress with Justin Guitar. But I&amp;rsquo;m still playing guitar approximately every day as I noodle or write songs. I also played guitar in worship bands a few times, which I enjoy.&lt;/p>
&lt;p>Speaking of writing songs, it was a busy year. I don&amp;rsquo;t have an exact count, but I&amp;rsquo;m sure I wrote way more than four songs. Finishing them is a different story. I find that I enjoy the recording and mixing process, yet I&amp;rsquo;m quicker to spend my time writing than recording. In the end, I released one song on SoundCloud that I wrote with my friend James.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Take weekly piano lessons all year and perform in recitals.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Practice piano 30 minutes a day.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Jam with musical friends often.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Continue to play publicly in worship bands.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Grade 3.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Write four songs.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Release two mixed and mastered songs on SoundCloud.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Pass Royal Conservatory Piano level 1 exam.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Complete Justin Guitar Ear Training Grade 3 and Justin Guitar Theory Grade 5.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Organize and perform a small concert at a senior center.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Learn to use a looper with &lt;a href="https://learnpracticeplay.com/loop-school-signup/">this&lt;/a> course.&lt;/li>
&lt;/ul>
&lt;h2 id="looking-forward-to-2025">Looking Forward to 2025&lt;/h2>
&lt;h3 id="career-1">Career&lt;/h3>
&lt;p>My hypothesis is still that going to a co-working space would do wonders for my productivity and focus. This is going to be the year that I actually try one out.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Maintain a sustainable work-life balance.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Try out a co-working space.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Advance WASI Observe to phase 2.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Attend some local software engineering meetups.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Give another conference talk.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Build, publish, and write about one meaningful personal project that contributes to my career trajectory.&lt;/li>
&lt;/ul>
&lt;h3 id="digital-life">Digital Life&lt;/h3>
&lt;p>This is a new category that is the amalgamation of organization and mental health. It&amp;rsquo;s mostly just a bunch of shots in the dark about how I think I could improve my relationship with my phone.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Keep making use of Todoist and Obsidian.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Don&amp;rsquo;t use my phone between 9pm and 9am.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Spend one Sunday a month not using digital devices.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Try not using social media for a month.&lt;/li>
&lt;/ul>
&lt;h3 id="faith-1">Faith&lt;/h3>
&lt;p>My main focus this year is around growing in my discipleship. This looks like creating habits where I spend meaningful time praying and in scripture.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build a habit of reading the Bible and praying four mornings a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Read 12 books of the Bible.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Spend one Sunday a month sabbathing.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Spend one hour a week in solitude.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Do a weekend-long solitude retreat.&lt;/li>
&lt;/ul>
&lt;h3 id="finances-1">Finances&lt;/h3>
&lt;p>I&amp;rsquo;m very blessed that most of my financial goals feel like they&amp;rsquo;re just on auto-pilot. The most pressing concern is to continue to fine-tune how I&amp;rsquo;m deploying my capital. That may or may not look like buying more real estate.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Use a budgeting tool like &lt;a href="https://ynab.com">YNAB&lt;/a> all year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Save 25% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Donate 10% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Explore the opportunity of building a laneway suite in my backyard.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Have a down payment to purchase a rental property in 2026.&lt;/li>
&lt;/ul>
&lt;h3 id="music-1">Music&lt;/h3>
&lt;p>Continuing the theme of last year, I want to focus more on mastering piano rather than guitar. I&amp;rsquo;ve laid out no specific theory goals, but that is because I&amp;rsquo;m naturally learning the theory as I progress on piano.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Take piano lessons all year and perform in recitals.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Practice piano daily.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete RCM piano level 2.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Organize jam sessions with friends.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Release three mixed and mastered songs on SoundCloud.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Master the A shape barre chord on guitar.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Join the worship team at Celebration.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Complete Justin Guitar Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Organize and perform a small concert at a senior center over Christmas.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Learn to use a looper with &lt;a href="https://learnpracticeplay.com/loop-school-signup/">this&lt;/a> course.&lt;/li>
&lt;/ul>
&lt;h3 id="physical-health-1">Physical Health&lt;/h3>
&lt;p>I&amp;rsquo;ve realized that completing an Ironman and doing a muscle-up are two lifelong fitness dreams I have. I may never do them, but I want to try. My goals this year should help point me in that direction.&lt;/p>
&lt;p>The other big thrust I want to make is to clean up my diet. I already eat fairly healthy, but I have very little discipline around my diet. I want to improve the following things: First, focus on my calorie deficit/excess and be able to consciously control it. Second, make it a habit of eating enough protein. Third, make the hard decisions at the grocery store — if I don&amp;rsquo;t bring junk home, I only have to avoid it once.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Be able to do five pull-ups by the end of the year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do a strength workout at least once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do yoga every day for one month of the year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Finish my first marathon.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Finish faster than my brother-in-law in an Olympic triathlon.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Play hockey once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Establish more disciplined eating habits.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Race a half Ironman late in the season.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take some power skating lessons.&lt;/li>
&lt;/ul>
&lt;h3 id="reading-1">Reading&lt;/h3>
&lt;p>Not much to say, let&amp;rsquo;s go read some books.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 10+ books.&lt;/li>
&lt;/ul>
&lt;h3 id="relationships-1">Relationships&lt;/h3>
&lt;p>These are pretty similar goals to last year with the caveat that I&amp;rsquo;m setting goals that are easier to objectively mark. I hope this will also make them easier to build into my life schedule.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Prioritize and maintain relationships with my family and close friends.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Schedule lunches with friends and family in town once a month.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Hop on a call with a friend abroad once a month.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Host four dinner parties.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure-1">Travel and Adventure&lt;/h3>
&lt;p>Once again, I&amp;rsquo;m just listing a bunch of stretch goals as inspiration. My travel usually ends up being spontaneous or based on where I travel for work anyway.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Family canoeing trip in Bowron Lake Provincial Park.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Go to New York.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take dance lessons.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Travel Asia.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Visit family in New Zealand.&lt;/li>
&lt;/ul>
&lt;h3 id="writing-1">Writing&lt;/h3>
&lt;p>I&amp;rsquo;m setting a less ambitious writing goal this year because I&amp;rsquo;m finding it to be less of a priority in my life. I don&amp;rsquo;t feel the need to write just for the sake of writing. I do, however, want to keep publishing thoughtful pieces periodically.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 4+ non-personal blog posts.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Finish editing my grandmother&amp;rsquo;s stories and publish them.&lt;/li>
&lt;/ul>
&lt;h2 id="closing-thoughts">Closing Thoughts&lt;/h2>
&lt;p>I&amp;rsquo;m interested to see how long I keep doing these year-in reviews. They&amp;rsquo;re very helpful for me. They force me to think about my goals and hold me publicly accountable. But, they&amp;rsquo;re not perfect. Since they&amp;rsquo;re public, there are things I care about that I don&amp;rsquo;t write about because they&amp;rsquo;re too personal. They can also be a bit heavy-handed and painful to write. We&amp;rsquo;ll see what I&amp;rsquo;m feeling next year.&lt;/p>
&lt;p>Some of my goals for 2025 are just about the exact same as they were for 2024. They&amp;rsquo;re in a comfortable stasis. In other areas, I&amp;rsquo;m raising my expectations of myself. And to hold things in balance, I&amp;rsquo;m also pulling back in certain areas of my life to make room. Here&amp;rsquo;s to another year and looking back to see if I struck the right balance.&lt;/p></description></item><item><title>Three More Things You Should Buy and Use</title><link>/blog/2024/three-more-things-you-should-buy-and-use/</link><pubDate>Sat, 02 Nov 2024 00:00:00 +0000</pubDate><guid>/blog/2024/three-more-things-you-should-buy-and-use/</guid><description>&lt;p>I&amp;rsquo;m continuing my hit series&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> of &lt;a href="/blog/2022/three-things-you-should-buy-and-use">three things you should buy and use&lt;/a>.&lt;/p>
&lt;h1 id="alarm-clock">Alarm Clock&lt;/h1>
&lt;p>No, I don&amp;rsquo;t mean the app on your phone. Breakthroughs in technology have allowed for an alarm to be built in to a device that physically sits on your bedside table. The technology we have access to nowadays is truly a marvel.&lt;/p>
&lt;p>Putting the sarcasm aside, spending $10 on a cheap alarm clock can make a drastic difference in your digital health by helping to keep your smartphone out of your bedroom. You no longer have the excuse of &amp;ldquo;needing it in there to be your alarm&amp;rdquo; and then next thing you know you just binged an hour of YouTube before trying to fall asleep. Take things one step further and move your phone charger from where it likely is — your bedside table — and move it to a spot in your home you rarely visit.&lt;/p>
&lt;p>Buying an alarm clock and removing my charger from the room have had a massive impact on my productivity and mental health. I typically don&amp;rsquo;t look at my phone until after 10 a.m. which is big progress for me. Now I spend my mornings doing things other than looking at my phone.&lt;/p>
&lt;h1 id="bidet">Bidet&lt;/h1>
&lt;p>I must confess it feels a bit taboo to be talking about bidets on my blog — like I&amp;rsquo;m breaking some kind of rule. But, I&amp;rsquo;ve been so happy with my purchase of a bidet that I just have to tell you about it.&lt;/p>
&lt;p>After my first year of university I spent two months backpacking across Europe with a friend of mine. I still remember being baffled every time I came across a bidet. I held a lot of Western stigma towards bidets. It wasn&amp;rsquo;t until years later that my brother purchased one that I started to consider getting one myself.&lt;/p>
&lt;p>I won&amp;rsquo;t go into too much detail so here are the main reasons I think they&amp;rsquo;re great:&lt;/p>
&lt;ul>
&lt;li>It&amp;rsquo;s way cleaner.&lt;/li>
&lt;li>You save by using way less toilet paper.&lt;/li>
&lt;li>Modern bidet attachments are very reasonably priced and easy to install.&lt;/li>
&lt;/ul>
&lt;p>If you&amp;rsquo;re on the fence I think it&amp;rsquo;s time for you to get off it and try out a bidet. This is doubly true if you work from home!&lt;/p>
&lt;h1 id="salt-cellar-and-olive-oil-squeeze-bottle">Salt Cellar and Olive Oil Squeeze Bottle&lt;/h1>
&lt;p>Okay fine, you caught me, I&amp;rsquo;ve actually listed two things bringing the total to four. How perceptive of you. I simply couldn&amp;rsquo;t help myself though because these two things go together like peanut butter and jelly.&lt;/p>
&lt;p>Some readers may not be familiar with these items. A salt cellar (also sometimes known as a salt pig) is a small ceramic dish meant to hold salt for cooking. They&amp;rsquo;re typically covered or angled so as to keep things out of the salt. An olive oil squeeze bottle is a little more self explanatory. Picture one of those clear bottles used for holding ketchup, but full of olive oil.&lt;/p>
&lt;p>Typically you will fill a salt cellar with kosher salt rather than table salt. Kosher salt is easier to pinch with your fingers and measure out. I&amp;rsquo;ve become much better at salting my meals since I started using kosher salt and stopped using salt shakers. Putting your olive oil in a squeeze bottle saves you the hassle of taking the cap off your bottle of olive oil.&lt;/p>
&lt;p>These may sound like trivial improvements to you, but they really matter. I use salt and oil in almost every single meal that I cook. They are essential. Thus making the experience of using them slightly nicer is worth it.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Am I allowed to define something as a hit series when I&amp;rsquo;m unsure if anyone even reads this blog?&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Run The River Half Marathon Race Report</title><link>/blog/2024/run-the-river-half-marathon-race-report/</link><pubDate>Sat, 26 Oct 2024 00:00:00 +0000</pubDate><guid>/blog/2024/run-the-river-half-marathon-race-report/</guid><description>&lt;p>Here is my race report for the Run the River Half Marathon on September 22. This was the first half marathon I ever completed and I had a great time.&lt;/p>
&lt;h2 id="training">Training&lt;/h2>
&lt;p>This race was the capstone to the &lt;a href="/blog/2024/great-white-north-sprint-triathlon">two&lt;/a> &lt;a href="/blog/2024/do-north-sprint-triathlon-race-report">other&lt;/a> triathlons I completed this summer. I signed up for it with my brother Jon because we both wanted to race our first half marathon. I also figured since I was training for the other races I may as well do this one too.&lt;/p>
&lt;p>For my training I followed a training plan that my brother-in-law Patrick (an avid runner) built for me. The plan had me running three times a week for an 11-week build. The first run of the week was an easy run, the second was a tempo or interval sprints run, and the third was a long run. The program capped off with a peak week that had 29 km of volume, including an 18 km long run, which was followed by two weeks of tapering.&lt;/p>
&lt;p>It was really refreshing following a set training plan. I enjoyed being able to just check boxes rather than have to think every time I worked out about what I should do. All the interval and tempo training really kicked my butt, but I think it made a huge difference.&lt;/p>
&lt;p>Unfortunately, I injured myself during the peak week. After the 18 km run, the outside of my left foot started to bother me. It turned out to be some peroneal tendonitis flaring up again (I had first had this early in the season when I ran my first ever 10k). On the one hand, it was pretty bad — I could hardly walk for a few days and I had to completely stop training for the last two weeks of tapering. On the other hand I was lucky that it was just a mild overuse injury that I could rehab myself.&lt;/p>
&lt;p>Frustrated by the injury I poured my energy into rehabbing it for the two weeks before the race and I did my best to not focus on the &amp;ldquo;what ifs&amp;rdquo;. I was able to get one run in before the race weekend which gave me enough confidence to proceed.&lt;/p>
&lt;h2 id="race-day">Race Day&lt;/h2>
&lt;p>Race day was a beautiful and crisp fall day. I showed up early to the baseball stadium where the race was based out of and explored. In a total coincidence I ended up seeing two friends I had recently met, Grace and Caleb, who were also doing the half-marathon — more on this later. After a little bit of warming up, Jon arrived, and we finished warming up and stretching together. Already it was time to start the race and a &lt;a href="https://olympic.ca/2024/08/10/marco-arop-takes-silver-medal-in-mens-800m/">Canadian olympian&lt;/a> ran us out of the stadium which was neat.&lt;/p>
&lt;p>&lt;img src="/run-the-river-map.jpg" alt="Course map">&lt;/p>
&lt;p>This was my first mass running start and I was immediately surprised by how crowded it felt despite the relatively small size of the event. It felt like the first ten minutes was spent filtering through the crowd trying to find the right pace.&lt;/p>
&lt;p>I had gone into this race with a goal time of 1:55, but I was unsure how things would go with my foot. Jon was looking to get a sub 2:00 time. We figured that we would run together as long as we could and we could always split off from each other as necessary. This ended up happening much sooner than expected.&lt;/p>
&lt;p>Almost immediately from the start I was pushing a harder pace than Jon was. I attribute this mostly to our devices reading slightly differently. My watch kept reading slower than his phone and so I kept feeling the need to speedup. After about 3 km Jon called it and didn&amp;rsquo;t want to keep matching pace with me. I had to decide to forge ahead or stay back with him. Fortunately, my friends Grace and Caleb happened to be right there moving at my pace so I decided to stick with them and forge ahead.&lt;/p>
&lt;p>For the next 7 km I drafted off Grace and Caleb. It was helpful to have them pacing me too — although we were going at a pace that was faster than I thought might be sustainable for the whole race. Naively I kept it up.&lt;/p>
&lt;p>At the 10 km mark my friends started to hit a wall and slow down, but I was feeling good so I kept up the same pace and went out on my own. Things were going great, but again foolishly I was starting to speed up without having others pacing me.&lt;/p>
&lt;p>Throughout the whole race I had a pretty simple nutrition and hydration plan. For nutrition I had three energy gels that I was taking on a 30 minute interval throughout the race in addition to a banana right before the race. For hydration I just had a sip of water each time I passed the one aid station. Unfortunately, the last time I botched the water handoff and I was very frustrated because I was very thirsty.&lt;/p>
&lt;p>Things got interesting at the 16 km mark. I absolutely slammed into a wall. It was like nothing I&amp;rsquo;ve ever felt before in my life. Everything started falling apart all at once; my energy levels fell off a cliff and I felt like I couldn&amp;rsquo;t get enough air; my legs felt like lead and each step was a concerted effort; and worst of all the mental effort was immense. All I wanted to do was stop and walk. Anytime I saw someone walking I would get mad inside because I so desperately wanted to do what they were doing.&lt;/p>
&lt;p>Throughout the whole race I was concerned about my foot, but miraculously it was fine the whole race. At times I felt some pain, but nothing I couldn&amp;rsquo;t push through. To keep my mind off my foot (and the rest of the pain) I spent most of the race meditating and praying. I probably recited the Lord&amp;rsquo;s Prayer 20 times.&lt;/p>
&lt;p>Despite hitting the wall I gave it my all and kept pushing. My pace began to meaningfully slow, but I kept going. Once my watch ticked over the 21.1 km the mental game got even harder. As I entered into the stadium and rounded the bases to the finish line I was moving so slowly. All I could think about was crossing that finish line and lying down.&lt;/p>
&lt;img src="/run-the-river-running.jpg" width="50%">
&lt;p>And that&amp;rsquo;s what I did. I basically just collapsed after they put a medal on me and there I laid by my family out of breath and in pain. Both my calves and quads were incredibly tense. It felt like if I moved them any more I would cramp up. I probably laid there for a good 10 minutes before I even dared to move again.&lt;/p>
&lt;h2 id="post-race">Post Race&lt;/h2>
&lt;p>I spent ~80% of the race in zone 5 with my heart rate pinned up to 170-180 which certainly explains why I felt so terrible. I trained well for the race, but in my hubris raced harder than I should have.&lt;/p>
&lt;p>After the race I took an ice bath and did lots of stretching which helped. Even still, I could barely walk for two days after the race. Even more surprisingly I feel like it took a full three weeks for my body to completely recover to normal. I went for a short and slow run one week after the race expecting it to be fine and ended up getting destroyed by it. Next time I do a race like this, I&amp;rsquo;ll be more aware of how long it takes to fully recover. I found this running-only event to be much more taxing on my body than a mixed event like at triathlon.&lt;/p>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>My final time for the half marathon was 1:53:43, a result I was very pleased with.&lt;/p>
&lt;p>As per usual, if you had asked me right after the race I would have told you that I was never running again. Now with some time between me and the pain I already have aspirations of doing a marathon!&lt;/p></description></item><item><title>Configuring AKS NGINX Ingress Controller</title><link>/blog/2024/configuring-aks-nginx-ingress-controller/</link><pubDate>Tue, 22 Oct 2024 00:00:00 +0000</pubDate><guid>/blog/2024/configuring-aks-nginx-ingress-controller/</guid><description>&lt;p>Every time I try to set up ingress into an AKS cluster, I forget how, so I thought I&amp;rsquo;d quickly write down the steps. This tutorial assumes you&amp;rsquo;re familiar with Kubernetes concepts and interacting with a cluster.&lt;/p>
&lt;h2 id="prerequisites">Prerequisites&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>A running &lt;a href="https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-portal?tabs=azure-cli">AKS cluster&lt;/a>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>kubectl&lt;/code> &lt;a href="https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-portal?tabs=azure-cli#connect-to-the-cluster">config&lt;/a> for the cluster setup.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Helm installed on your machine.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The application you want to expose running in the cluster. For the purposes of this tutorial, we&amp;rsquo;ll be working with the following sample application.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#309;font-weight:bold">apiVersion&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>apps/v1&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">kind&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>Deployment&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">metadata&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">name&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>sample-app&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">spec&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">selector&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">matchLabels&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">app&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>http-echo&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">template&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">metadata&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">labels&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">app&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>http-echo&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">spec&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">containers&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#309;font-weight:bold">name&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>http-echo&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">image&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>hashicorp/http-echo:1.0.0&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">args&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#c30">&amp;#34;-text=Hello, World!&amp;#34;&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">ports&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#309;font-weight:bold">containerPort&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#f60">5678&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;h2 id="installing-nginx-ingress-controller">Installing NGINX Ingress Controller&lt;/h2>
&lt;p>Start by installing the NGINX ingress controller.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm install ingress-nginx ingress-nginx/ingress-nginx &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --create-namespace &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --namespace ingress-nginx &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --set controller.service.annotations.&lt;span style="color:#c30">&amp;#34;service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path&amp;#34;&lt;/span>&lt;span style="color:#555">=&lt;/span>/healthz &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --set controller.service.externalTrafficPolicy&lt;span style="color:#555">=&lt;/span>Local
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="callout">
Note that the &lt;a href="https://learn.microsoft.com/en-us/answers/questions/1166380/why-aks-loadbalancer-created-wrong-health-check-pa">special annotation&lt;/a> is required.
&lt;/div>
&lt;p>Now install cert-manager.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>helm repo add jetstack https://charts.jetstack.io
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm repo update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>helm install &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> cert-manager jetstack/cert-manager &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --namespace cert-manager &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --create-namespace &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --version v1.16.1 &lt;span style="color:#c30;font-weight:bold">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#c30;font-weight:bold">&lt;/span> --set crds.enabled&lt;span style="color:#555">=&lt;/span>&lt;span style="color:#366">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll need to configure a ClusterIssuer in order for the certificate provisioning to work.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#309;font-weight:bold">apiVersion&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>cert-manager.io/v1&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">kind&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>ClusterIssuer&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">metadata&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">name&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>letsencrypt-prod&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">spec&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">acme&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#09f;font-style:italic"># The ACME server URL&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">server&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>https://acme-v02.api.letsencrypt.org/directory&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#09f;font-style:italic"># Email address used for ACME registration&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">email&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&amp;lt;YOUR EMAIL HERE&amp;gt;&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#09f;font-style:italic"># Name of a secret used to store the ACME account private key&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">privateKeySecretRef&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">name&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>letsencrypt-prod&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#09f;font-style:italic"># Enable the HTTP-01 challenge provider&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">solvers&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#309;font-weight:bold">http01&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">ingress&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">ingressClassName&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>nginx&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="setting-up-ingress">Setting Up Ingress&lt;/h2>
&lt;p>Before creating an ingress resource we need to put a service in front of our deployment.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#309;font-weight:bold">apiVersion&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>v1&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">kind&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>Service&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">metadata&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">name&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>sample-app&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">spec&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">type&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>ClusterIP&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">selector&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">app&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>http-echo&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">ports&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#309;font-weight:bold">protocol&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>TCP&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">port&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#f60">5678&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">targetPort&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#f60">5678&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now we create the ingress resource.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#309;font-weight:bold">apiVersion&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>networking.k8s.io/v1&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">kind&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>Ingress&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">metadata&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">name&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>sample-app&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">annotations&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">cert-manager.io/cluster-issuer&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>letsencrypt-prod&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb">&lt;/span>&lt;span style="color:#309;font-weight:bold">spec&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">ingressClassName&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>nginx&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">rules&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#309;font-weight:bold">host&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&amp;lt;YOUR DOMAIN HERE&amp;gt;&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">http&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">paths&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#309;font-weight:bold">path&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>/&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">pathType&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>Prefix&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">backend&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">service&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">name&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>sample-app&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">port&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">number&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#f60">5678&lt;/span>&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">tls&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &lt;span style="color:#309;font-weight:bold">hosts&lt;/span>:&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>- &amp;lt;YOUR DOMAIN HERE&amp;gt;&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bbb"> &lt;/span>&lt;span style="color:#309;font-weight:bold">secretName&lt;/span>:&lt;span style="color:#bbb"> &lt;/span>sample-app-tls&lt;span style="color:#bbb">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="dns">DNS&lt;/h2>
&lt;p>The only thing left to do is to setup an A record in your DNS provider. Create an A record that points from your domain to the external IP address of the ingress controller.&lt;/p>
&lt;div class="callout">
If your domain was something like &lt;code>foo.com&lt;/code> than you&amp;rsquo;d want to map &lt;code>*&lt;/code> to the IP. However, if your domain was something like &lt;code>bar.foo.com&lt;/code> than you&amp;rsquo;d want to map &lt;code>bar&lt;/code> to the IP.
&lt;/div>
&lt;p>Now you should be able to receive secure traffic to your application on your domain.&lt;/p></description></item><item><title>Do North Sprint Triathlon Race Report</title><link>/blog/2024/do-north-sprint-triathlon-race-report/</link><pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate><guid>/blog/2024/do-north-sprint-triathlon-race-report/</guid><description>&lt;p>Here is my race report for the Do North Sprint Triathlon on August 24.&lt;/p>
&lt;h2 id="training">Training&lt;/h2>
&lt;p>I signed up for this triathlon on a whim after completing my &lt;a href="/blog/2024/great-white-north-sprint-triathlon-race-report">first one&lt;/a> because I thought doing a triathlon in the Edmonton River Valley sounded lovely and I figured I could build on my pre-existing training. Simultaneously, I was also training for a half marathon which meant most of my training load was in the run. Overall I went for 10 swims, 19 runs, and 4 rides. I only did two brick sessions, but I was less worried about this given that it wasn&amp;rsquo;t my first triathlon.&lt;/p>
&lt;p>Overall I think I did a good job of compounding my efforts from the earlier triathlon while still juggling a high running load for the half marathon. My only regret was training too little on the bike because it was noticeable during the race.&lt;/p>
&lt;h2 id="race-day">Race Day&lt;/h2>
&lt;p>Race day was nice and cool with thick cloud coverage. The night before there was a massive thunderstorm, but thankfully the weather had moved along. I woke up early, and made the convenient jaunt down to the Kinsmen Park and setup my transition area. I enjoyed not having to set this all up on a previous day. After that was all settled, I made my way to the pool where the race would start. While I waited for my heat to start, I made some friends and we had some nice chitchat.&lt;/p>
&lt;p>&lt;img src="/do-north-map.png" alt="Course map">&lt;/p>
&lt;h3 id="swim">Swim&lt;/h3>
&lt;p>The swim was organized to have four swimmers to a lane. When a lane fully emptied it would be filled up with four more swimmers. I was with two strong swimmers and a weaker swimmer. I quickly found my place in the lane and settled into the swim. The mantra I focused on during the swim was &amp;ldquo;calm&amp;rdquo; and I excelled in this mission. While I did leave some performance on the table it was totally worth starting the race out in a calm and controlled manner. Unsurprisingly, the closed water environment was a big factor in my ability to stay calm. Overall I would say the bottleneck in my swim was my anxiety.&lt;/p>
&lt;p>Once the counter signalled I was finished (it was very nice having someone counting your laps), I climbed out of the pool and made my way to T1. My transition was a bit slower than I wished because I had to change out of a swimsuit (I should really invest in a tri-suit), but otherwise I was feeling really good. One of the videographers followed me the entire transition, which I thought was hilarious — it was basically just two minutes of me struggling to get socks on my wet feet.&lt;/p>
&lt;h3 id="bike">Bike&lt;/h3>
&lt;p>After running my bike out of the transition area it was on to the beautiful bike course which took you across Walterdale Bridge, along River Valley Road, and up Groat Road. I bike this route all the time, however getting to ride on the road was a special treat. On my first pass out west, I thought I was biking into a headwind, but I was sorely mistaken. As soon as I turned around on Groat Road to head down, I discovered that there was in fact a massive headwind the other way. It was so strong that it was difficult to pick up speed going down hill.&lt;/p>
&lt;p>Most of the ride was a game of ping pong between another racer and myself. I would pass him going up the hill and then he would pass me going down the hill and on the flats thanks to his skinny tires and aero bars. I just barely managed to pass him at the end of the bike course before a no-pass zone, which felt good. This was all thanks to my biking mantra of &amp;ldquo;push&amp;rdquo;. In my first triathlon I played the bike conservatively. I was wary of pushing too hard and have nothing left in the tank for the run. This time I decided to risk it and see how hard I could go on the bike and then just hope for the best on the run. This paid off with some pretty sweet pace on the bike. My lack of bike training did come back to bite me here though. My legs were very clearly a bottleneck for how hard I could push on the bike. I think with more training I could have been cardio limited and hit a better time.&lt;/p>
&lt;p>It was also on the ride that I took all my nutrition. I popped a gel at the start of the ride and another right before I got off the bike. T2 went smoothly and thankfully this time unfilmed.&lt;/p>
&lt;h3 id="run">Run&lt;/h3>
&lt;p>Coming out of T2 my legs were absolute jello and it took a good 500 meters to get that sorted out. At which point it was time to climb the big hill on the run course. Why they decided to put this hill in the route I will never know. Why they made us do it twice&amp;hellip; I was beside myself.&lt;/p>
&lt;p>Despite my gripes with the hill, I managed to hold a respectable pace and just focused on my mantra &amp;ldquo;dig&amp;rdquo;. Thankfully the bottleneck on this leg was my &amp;ldquo;cardio&amp;rdquo; which in my opinion makes for a more comfortable run than one where your legs are tired. This meant that my heart rate was pinned in the high 170s the whole time. In the last 300 meters I pushed hard and passed a few people. Just like that I was past the finish line.&lt;/p>
&lt;p>&lt;img src="/do-north-running.jpg" alt="Me running">&lt;/p>
&lt;h2 id="post-race">Post Race&lt;/h2>
&lt;p>A lesson I learned from my first triathlon was that I had to do a much better job taking care of my body post race. So this time I stretched every muscle for 2 minutes apiece and I also took advantage of some free leg compression therapy they were offering at the finish line. With all of this I felt noticeably better than I did last time.&lt;/p>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>My &lt;a href="https://zone4.ca/race/2024-08-24/8b1ada92/results">results&lt;/a> were as follows:&lt;/p>
&lt;ul>
&lt;li>750 m swim in 18:35.&lt;/li>
&lt;li>21 km bike in 37:17.&lt;/li>
&lt;li>5 km run in 27:30.&lt;/li>
&lt;li>Total time of 1:27:36.&lt;/li>
&lt;li>Finished 8/13 in the 20-29 male category.&lt;/li>
&lt;/ul>
&lt;p>Overall I&amp;rsquo;m super happy. I was faster on a harder course and felt better afterwards. I&amp;rsquo;m looking forward to the next one.&lt;/p></description></item><item><title>Great White North Sprint Triathlon Race Report</title><link>/blog/2024/great-white-north-sprint-triathlon-race-report/</link><pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate><guid>/blog/2024/great-white-north-sprint-triathlon-race-report/</guid><description>&lt;p>On March 25, 2024 I signed up to compete in the &lt;a href="https://platinumracing.ca/great-white-north/">Great White North Sprint Triathlon&lt;/a>. This decision was preceded by months of me mulling over competing in my first ever triathlon. Race day (July 7) has come and gone and I&amp;rsquo;m so glad I decided to participate. Here is my race report.&lt;/p>
&lt;h2 id="training">Training&lt;/h2>
&lt;p>I had just about three and a half months to train for my first ever triathlon. This was adequate, but I wouldn&amp;rsquo;t have wanted any less time to train given my relative inexperience. The biggest takeaway from my training was that I should have used a formal plan. I always intended to choose and follow a plan, but every time I looked for one I was intimated by how complex they were. Instead I just winged it — this was a big mistake.&lt;/p>
&lt;p>I was most nervous for the swim because I was the least experienced and confident at swimming — especially in open water. I ended up doing 10 swims total, each slowly building up endurance. In terms of distance I swam about 10 km. Only one of my training sessions was in open water and none of them were with my wetsuit (in fact I barely managed to procure a wetsuit in time for the race). I should have trained my swim more — in open water — with more sophistication&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. A couple lessons focused on improving my form also would have gone a long way.&lt;/p>
&lt;p>I was not at all concerned about the biking portion of the race. The only reason I trained here was because I enjoy biking and because I knew this was where I would make up the most time on the race day. I ended up doing 14 rides for a total distance of 307 km. If that seems like way more distance than it should be that is because I snuck a metric century in there for fun. My biggest mistakes here were not spending enough time in aero position to get used to it and not doing enough intervals/tempo work&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>If I was nervous for the swim then I was apprehensive for the run. For many years I&amp;rsquo;ve avoided running due to bad knees, but I was pleasantly surprised to see that my knees were a non-issue in my training and on race day. Despite having no training plan I did my best to slowly add running load so I wouldn&amp;rsquo;t trigger any injuries. My first runs were very slow and trepidatious 2k&amp;rsquo;s. Overall I did 25 runs for a total distance of 118 km. I was pleasantly surprised to discover that I quite like running — so long as I run with someone or have a podcast pumped into my ears. Again my approach was very brain dead. No sprints/tempo/interval work, very little science in the load or pace, etc.&lt;/p>
&lt;p>Despite my many missteps I was pleased with the brick training I did. Throughout my training I did 7 brick training sessions. These ended up being really helpful. In one of the bricks I did all three in a try-a-tri distance which gave me a lot of confidence. The big takeaways for next time are:&lt;/p>
&lt;ul>
&lt;li>Follow a training plan.&lt;/li>
&lt;li>Train more.&lt;/li>
&lt;li>Focus more on the swim and less on the bike and run.&lt;/li>
&lt;li>Train more in open water.&lt;/li>
&lt;/ul>
&lt;h2 id="race-day">Race Day&lt;/h2>
&lt;p>The race day was a beautiful, warm, sunny, and windy day. Since T1 and T2 were at different locations I had already racked my bike the previous day&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. The sprint distance was the last heat to go so I anxiously waited around in the morning watching other racers. I was very anxious about not having enough time to put on my wetsuit so in a rookie move I put it on way too early and baked in the sun. Right before the heat they allowed us to splash around in the water to acclimatize. That was helpful and prevented me from dealing with any cold shock (in addition to the wetsuit and the water only being 18 degrees celsius).&lt;/p>
&lt;img src="/gwn-wetsuit.jpg" width="60%">
&lt;h3 id="swim">Swim&lt;/h3>
&lt;p>&lt;img src="/gwn-swim.png" alt="Swim course map">&lt;/p>
&lt;p>After a quick countdown my first ever triathlon was underway. I had purposely seeded myself to the back of the pack and so getting into the water was very anti-climactic. It was my intention to swim at a slow pace, but in the excitement I started my swim off much too fast. I quickly passed a few people which put me in the thick of things. About 200m in another swimmer started bumping into my leg over and over again so I dipped out of the way. But, the combination of going too fast and this dodging maneuver made me lose my breathing pattern. Suddenly I couldn&amp;rsquo;t do the front crawl I had practiced. Rather, I was swimming with my head out of the water. I was aware enough to realize that I was starting to panic, but I wasn&amp;rsquo;t able to calm myself down.&lt;/p>
&lt;p>In my panicked state I just switched to a mix of back crawl and head up front crawl for the rest of the swim. Unfortunately, I zig-zagged terribly on my back and I found sighting incredibly difficult because I had never practiced. It didn&amp;rsquo;t help that my swim cap was coming off my head pulling my goggles off my eyes. Honestly, I was pretty terrified out there. Had the swim been much longer I think there is a reasonable chance I would have given up. Wearing a wetsuit was a godsend because of the extra buoyancy.&lt;/p>
&lt;p>When I finally made it to the shore I dragged myself out of the water. I almost kissed the ground. For the most part T1 went well. The one hiccup was that I couldn&amp;rsquo;t get the hair tie out of my hair and my bun was too high to fit under my helmet. I ended up just pushing the bun down the back of my head and doing the rest of the race with my hair in this messy state.&lt;/p>
&lt;p>There was lots to improve about my swim — primarily swimming slower. But, I&amp;rsquo;m pleased that the abysmal start didn&amp;rsquo;t wreck the rest of my race.&lt;/p>
&lt;h3 id="bike">Bike&lt;/h3>
&lt;p>&lt;img src="/gwn-bike.png" alt="Bike course map">&lt;/p>
&lt;p>Once I was on my bike I was feeling more comfortable. My mantra for this leg of the race was &amp;ldquo;high cadence&amp;rdquo; and &amp;ldquo;aero position&amp;rdquo;. I wanted to focus on keeping my cadence up to save my legs and obviously an aero position is important. I feel like this mantra really helped and the aero position was noticeably helpful. The overall route had a couple of hills, but mostly was a gentle downhill. I passed about six people near the beginning of the route and towards the end I was passed by two people. For the most part though I was alone on the course.&lt;/p>
&lt;p>My primary concern with the bike was making sure that I had enough left in the tank for the run. Lots of guesswork was involved. At one point on the ride I felt some tightness in my right hamstring, but thankfully that dissipated before the ride was over.&lt;/p>
&lt;p>One mistake I made on the ride was my hydration. I did an okay job fueling — I ate a race energy bar. For hydration I had a water bottle with an electrolyte tab in it. However, I thought it would be good to have some energy in the water too so I had put some sugar in the bottle. While this is a good idea in principle it made the water really gross because it was lukewarm. On the ride I just wished I had plain water.&lt;/p>
&lt;p>Overall the bike was a success. I was blown away how much faster the half-ironman racers were, but I was proud of what I did. After the race I ended up discovering that my back tire was flat. I had picked up a slow leak from a staple on the road. I was very lucky that it didn&amp;rsquo;t interrupt my race.&lt;/p>
&lt;h3 id="run">Run&lt;/h3>
&lt;p>&lt;img src="/gwn-run.png" alt="Run course map">&lt;/p>
&lt;p>I rolled into T2 feeling good. Other than nearly forgetting my number belt I had a smooth transition that was aided by some family cheering me on. At this point in the day it was starting to get very warm and I was feeling it. I was also feeling zonked after the swim and bike so I settled into what felt like a slow but reasonable pace. I was pleased that it felt like it was my cardio limiting me and not weakness in my legs.&lt;/p>
&lt;p>The course was a there-and-back situation and the turnaround point felt so far away. I definitely had a pain-cave-lite experience on the run. Thankfully all the other runners were very encouraging which was fun to be part of. In the last 1.25 km I started to feel some shivers from the heat and I definitely had not drank as much water as I should have.&lt;/p>
&lt;p>Sooner than anticipated I passed the finish line and what a great feeling that was! It was great to have some of my family there to celebrate as well.&lt;/p>
&lt;h2 id="post-race">Post Race&lt;/h2>
&lt;p>I pretty much screwed up everything about the post race that I could. I didn&amp;rsquo;t stretch and I did a poor job of rehydrating. Consequently I totally bonked after the race and felt pretty bad for a couple days after. This was a big lesson learned for me — in the future I&amp;rsquo;ll be much more diligent about this.&lt;/p>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>My &lt;a href="https://zone4.ca/race/2024-07-07/5a9c816e/results/facb5cc0">results&lt;/a> were as follows:&lt;/p>
&lt;ul>
&lt;li>750 m swim in 18:15.&lt;/li>
&lt;li>21 km bike in 47:57.&lt;/li>
&lt;li>5 km run in 28:38.&lt;/li>
&lt;li>Total time of 1:34:49.&lt;/li>
&lt;li>Finished 4/5 in the 19-29 male category.&lt;/li>
&lt;/ul>
&lt;h2 id="another-one">Another One?&lt;/h2>
&lt;p>If you had asked me this question five minutes after I finished the race I would have given you a firm, &amp;ldquo;No thank you sir, good day!&amp;rdquo; But, with the exhaustion of the race solidly in the rear view mirror, I&amp;rsquo;m sure I&amp;rsquo;ll do another one. In fact I&amp;rsquo;ve already signed up for the Do North Triathlon later this summer and a half marathon in the fall. Beyond that I definitely have aspirations of competing in longer distance triathlons too.&lt;/p>
&lt;p>We&amp;rsquo;ll see where it takes me. For now I&amp;rsquo;m just basking in the feeling of achievement and the joy of picking up a new sport.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>A standard swim session for me looked like spending the first 15 minutes trying to get my swim cap on, swimming 750 m at a medium pace, and then chilling in the steam room.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Read as zero.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>Which included me wading through all the confusion that is setting up your transition zones for the first time as a new triathlete.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Four Remote Communication Anti-patterns to Avoid</title><link>/blog/2024/four-remote-communication-anti-patterns-to-avoid/</link><pubDate>Tue, 18 Jun 2024 00:00:00 +0000</pubDate><guid>/blog/2024/four-remote-communication-anti-patterns-to-avoid/</guid><description>&lt;p>Communication is hard. It&amp;rsquo;s even harder when you&amp;rsquo;re working remotely. Not only do you lose out on the benefits of talking to someone face-to-face, but you also have to deal with the challenges of asynchronous communication.&lt;/p>
&lt;p>You need every advantage possible when communicating remotely so here are four remote communication &lt;a href="https://en.wikipedia.org/wiki/Anti-pattern">anti-patterns&lt;/a> you should avoid.&lt;/p>
&lt;h2 id="overusing-dms">Overusing DMs&lt;/h2>
&lt;p>Most messages don&amp;rsquo;t actually need to be private. If the message is directed at one person it is still better in a public channel — you can just tag them. Obviously certain sensitive or entirely irrelevant topics should be kept to DMs, but default to public.&lt;/p>
&lt;p>Sending a message in a public channel rather than in a DM greatly expands the pool of people who can: answer your questions; learn from the answers; or provide other feedback. An added bonus is that the conversation becomes publicly indexed and searchable.&lt;/p>
&lt;p>People are often scared to ask questions publicly because they don&amp;rsquo;t want to look dumb. First, who cares what others think. Second, do you really want to work at a place where people would think that? Regardless, in my experience, people often have the same question and are grateful when someone asks it.&lt;/p>
&lt;p>People often also object to the amount of noise this creates in public channels. That is easily mitigated by liberally using threads.&lt;/p>
&lt;p>❌ DMing Jenna to ask what the plan for the new frobulator project is.&lt;/p>
&lt;p>❌ DMing Jenna and a few relevant colleagues in a group chat to ask what the plan for the new frobulator project is.&lt;/p>
&lt;p>✅ Asking in #engineering what the plan for the new frobulator project is and tagging Jenna.&lt;/p>
&lt;h2 id="saying-hi-first">Saying Hi First&lt;/h2>
&lt;p>This is a &lt;a href="https://nohello.net/en/">well documented&lt;/a> anti-pattern. The idea is that saying hi introduces an unnecessary cycle to a conversation slowing it down. Instead of getting right down to business the responder is forced to say hi back and everyone&amp;rsquo;s time is wasted. It&amp;rsquo;s nice to be polite, but save everybody the time.&lt;/p>
&lt;p>❌ &amp;ldquo;Hey 👋&amp;rdquo;&lt;/p>
&lt;p>❌ &amp;ldquo;Hi Jenna, do you have a minute to chat?&amp;rdquo;&lt;/p>
&lt;p>❌ &amp;ldquo;Can I ask you a question about frobulator?&amp;rdquo;&lt;/p>
&lt;p>✅ &amp;ldquo;Hey, did you get the tests passing on frobulator?&amp;rdquo;&lt;/p>
&lt;h2 id="sending-terse-messages">Sending Terse Messages&lt;/h2>
&lt;p>When you&amp;rsquo;re talking to someone face-to-face it&amp;rsquo;s often best to make your point simply and clarify afterwards with any necessary context. This is because in-person you can immediately tell if someone is confused and quickly clarify. In an asynchronous remote environment, however, you can&amp;rsquo;t easily tell when someone is confused and the unavoidable time delay of asynchronous communication makes it slow to clarify.&lt;/p>
&lt;p>Sending a terse message is just asking to be misunderstood. Similar to saying hi first it adds more unnecessary cycles to a conversation.&lt;/p>
&lt;p>Instead, prefer to send messages that are clear and carry the necessary context up front. It feels like a lot of work typing it all out, but in the end it saves everybody time.&lt;/p>
&lt;p>❌ &amp;ldquo;No&amp;rdquo;&lt;/p>
&lt;p>❌ &amp;ldquo;The tests are failing&amp;rdquo;&lt;/p>
&lt;p>✅ &amp;ldquo;Not yet. I wrote another two tests for frobulator, but they&amp;rsquo;re all still failing. I think they&amp;rsquo;re failing because the reticulator isn&amp;rsquo;t properly calibrated. Maybe @cassandra can help?&amp;rdquo;&lt;/p>
&lt;h2 id="tiptoeing-around-timezones">Tiptoeing Around Timezones&lt;/h2>
&lt;p>In a remote settings people can be in drastically different timezones. 10am for you might be the middle of the night for someone else. Some people avoid sending messages to or tagging someone when it is not their working hours. This is done in an effort to protect other people&amp;rsquo;s work life balance. While this is well intentioned it is ultimately harmful.&lt;/p>
&lt;p>It is not your responsibility to protect someone else&amp;rsquo;s work life balance. You should communicate as you need and others can manage their own time. Tools like muting notifications and do not disturb exist for a reason. Think of it like the opposite of &lt;a href="https://en.wikipedia.org/wiki/Robustness_principle">Postel&amp;rsquo;s law&lt;/a>: be liberal in how you communicate, be conservative in what you accept from others.&amp;quot;&lt;/p>
&lt;p>❌ Waiting until tomorrow morning to provide your manager with feedback on the frobulator project so that he doesn&amp;rsquo;t get a message in the middle of the night.&lt;/p>
&lt;p>❌ Using a scheduled message to send your feedback on the frobulator project at 8am their time.&lt;/p>
&lt;p>✅ Immediately messaging your manager with your feedback on the frobulator project so that you can move on to other tasks.&lt;/p></description></item><item><title>The Future of AR and Remote Work</title><link>/blog/2024/the-future-of-ar-and-remote-work/</link><pubDate>Sun, 10 Mar 2024 00:00:00 +0000</pubDate><guid>/blog/2024/the-future-of-ar-and-remote-work/</guid><description>&lt;p>Like every other geek on earth right now the Apple Vision Pro has me excited. And though I haven&amp;rsquo;t tried one yet, it has me excited about how AR&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> could change the future of remote white collar work.&lt;/p>
&lt;p>There is a lot to love about remote work: having no commute; freely choosing where you live; making your own hours; and much more. But, remote work has some serious disadvantages too. In my experience the two most glaring issues are the lack of boundaries and the isolation&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>. As we continue to watch AR evolve with moments like the release of the Apple Vision Pro I&amp;rsquo;m becoming increasingly convinced that AR may have a solution to these issues.&lt;/p>
&lt;p>Let&amp;rsquo;s focus on the issue of a lack of boundaries first. In a traditional work setup when you work from an office you&amp;rsquo;re given very clear physical boundaries. You can take the mental aspects of
your work home with you but the intent is that work happens at the office and your personal life happens at home. Working from home shatters these boundaries and everything starts to bleed together. Suddenly you&amp;rsquo;re playing video games in the middle of the day and checking Slack right before bed.&lt;/p>
&lt;p>Small improvements can be made. You&amp;rsquo;re better off working in your living room rather than your bedroom. And you&amp;rsquo;re even better off working in a dedicated room. But, an interior wall in your home isn&amp;rsquo;t a very big boundary between work and your personal life.&lt;/p>
&lt;p>Others seek the solution of co-working spaces to provide this boundary for themselves. While it certainly does introduce a boundary it puts a commute back in your life and perhaps more importantly means you&amp;rsquo;re working on a laptop all day — not your lovingly crafted home office with dual monitors and an ergonomic chair.&lt;/p>
&lt;p>Here&amp;rsquo;s where sufficiently advanced AR could come in to solve this issue of a lack of boundaries in two ways. First, you could use your AR headset to work in the savannah or on the moon. At the end of the day you take off the headset and you&amp;rsquo;re back at home. I reckon that the technology will get convincing enough that this will feel like a better boundary. Second, once an AR headset becomes a better setup than your dual monitors you are no longer chained to your home. Now whenever working from your home isn&amp;rsquo;t feeling right you can easily step out to your local coffee shop and be just as productive.&lt;/p>
&lt;p>Now let&amp;rsquo;s tackle the issue of isolation. From my perspective this is the biggest issue with remote work. The obvious angle on this is that remote work is lonely — which it is. But, even worse I think that remote work has stolen my sense of camaraderie. I&amp;rsquo;m talking about the feeling you have when you and all your friends are about to write an exam you prepared for all semester; or the feeling of white boarding out a really difficult engineering problem with some coworkers.&lt;/p>
&lt;p>You would expect working at a fast moving tech startup to offer this camaraderie in spades. But, it just doesn&amp;rsquo;t deliver the way I thought it would and being remote is to blame. The emotional effect of anything done over Zoom is dulled.&lt;/p>
&lt;p>The obvious solution is to find this camaraderie in other ways such as joining a meetup. This is certainly a viable option, but it can be tough to find people interested in what you&amp;rsquo;re interested in when remote work lets you live on the ends of the earth. In any case this doesn&amp;rsquo;t address the root issue which is the majority of my day at work lacks camaraderie.&lt;/p>
&lt;p>Once again AR might just have the answers to our problems. Once AR is good enough to trick me into thinking that I&amp;rsquo;m physically with my coworkers, then I may as well not be remote. The camaraderie will naturally follow. Let&amp;rsquo;s imagine some scenarios: I need to design a system and so I white board with some other engineers; it&amp;rsquo;s time for my 1-1 with my manager and so I go for a virtual walk or to a virtual coffee shop with them; I&amp;rsquo;m feeling lonely and want to have some people around even if I still need to focus by myself and so I enter a virtual office where people can work side by side. Best of all I get to keep all the benefits of remote work at the same time.&lt;/p>
&lt;p>Will AR ever get sufficiently good to make these ideas come true? If so, how quickly will that happen? Will AR boundaries be able to fill the role of physical boundaries or will it still come down to personal decisions and habits? I don&amp;rsquo;t know the answers to these questions, but I sure am excited to see these questions answered as this industry evolves!&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>What I&amp;rsquo;m talking about has many names: augmented reality (AR), virtual reality (VR), spatial computing. Society hasn&amp;rsquo;t settled on what we&amp;rsquo;ll call it so I&amp;rsquo;m going to stick with AR because it feels the least conceited.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Shoutout to the Cortex podcast for inspiring me to think more deeply about this area.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Name Your Stuff</title><link>/blog/2024/name-your-stuff/</link><pubDate>Fri, 19 Jan 2024 00:00:00 +0000</pubDate><guid>/blog/2024/name-your-stuff/</guid><description>&lt;p>I like to name my stuff. I picked up the habit a few years ago when I purchased my first bike during the pandemic — a beautiful rose colored Kona Rove gravel bike. I named her &lt;em>Rose&lt;/em>. Some might find a name like that too on the nose, but I don&amp;rsquo;t mind an obvious name. I think there is beauty in its simplicity. That&amp;rsquo;s why when I bought my first car — a lovingly used ruby red Ford Escape — I unsurprisingly named her &lt;em>Ruby&lt;/em>.&lt;/p>
&lt;p>In the realm of technology I reach for names from some of my favorite science fiction. My M1 MacBook Pro has donned the name &lt;em>Rocinante&lt;/em> after the space frigate that carries around the plucky protagonist crew of the book series The Expanse. I named my Obsidian vault &lt;em>Gregorivich&lt;/em>. The name is taken from a near omniscient ship mind in the book To Sleep in a Sea of Stars. A fitting name for a repository of my knowledge.&lt;/p>
&lt;p>It&amp;rsquo;s true that the guitar I spent a year initially learning to play on went unnamed. But, I made sure to right my wrong when I purchased my first acoustic — a sleek Martin D-16e. He goes by &lt;em>Luther&lt;/em>, Martin &lt;em>Luther&lt;/em>. When the purchase of a Roland FP-30X digital piano followed suit I opted for a more subtle name: &lt;em>Miller&lt;/em>. Both an ode to the first artist I loved Tor Miller and a missionary my Grandmother knew in India named Roland Miller.&lt;/p>
&lt;p>I could list all sorts of reasons as to why you should name your stuff: it&amp;rsquo;s fun; it&amp;rsquo;s a way to express yourself; the personification helps you take better care of your things. But none of that really matters. You should name your stuff because you want to. Not every reader will want to. But, maybe you — yes you — will. And if that&amp;rsquo;s the case then what are you waiting for? Look up from your screen. Find something you love. And give it a name.&lt;/p></description></item><item><title>Year in Review: 2023</title><link>/blog/2023/year-in-review-2023/</link><pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate><guid>/blog/2023/year-in-review-2023/</guid><description>&lt;p>The year of 2023 was the first year in my life where I wasn&amp;rsquo;t a student. As can be expected with such a transition I had lots of fun and encountered some struggles along the way too. Without further adieu here are my top 10&amp;rsquo;s of the year.&lt;/p>
&lt;h2 id="top-10-experiences">Top 10 Experiences&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Following Jesus&lt;/strong> &amp;mdash; 2023 was the year I decided to follow Jesus. It was a big decision for me and I put a lot of thought into it.&lt;/li>
&lt;li>&lt;strong>Meeting my girlfriend&lt;/strong> &amp;mdash; 2023 was also the year that I met my girlfriend.&lt;/li>
&lt;li>&lt;strong>Trip to Peru&lt;/strong> &amp;mdash; In the fall I spent two weeks in Peru with my brother, sister-in-law, and their 6 month old son. Highlights included the great conversation, Machu Picchu, lots of delicious food, and spending so much time with my Nephew.&lt;/li>
&lt;li>&lt;strong>Trip to Scandinavia&lt;/strong> &amp;mdash; In the summer Fermyon had an offsite in Oslo, Norway. Since I was already being flown out to Europe I decided to tack some travel on before the trip. I spent one week working remotely in Copenhagen. Then I spent two weeks traveling around Helsinki, Stockholm, Gothenburg, Bergen, and Oslo. This was my first solo trip and I ended up really enjoying it. It pushed me out of my comfort zone and I met a lot of cool people. I also ate at so many top tier restaurants.&lt;/li>
&lt;li>&lt;strong>Fermyon off-sites and conferences&lt;/strong> &amp;mdash; I was very lucky to get to travel a lot for work (2 off-sites and 3 conferences). Even better, I can say that spending time in-person with my co-workers is a highlight experience for me. In addition to all of this I was also very excited to give my first ever conference talk this year.&lt;/li>
&lt;li>&lt;strong>Competitive Magic the Gathering&lt;/strong> &amp;mdash; Throughout the year I got into competitive Magic. This culminated in me qualifying for and playing in the Western Canada Regionals. I was one match win away from qualifying for day two which I thought was very good for my first ever tournament.&lt;/li>
&lt;li>&lt;strong>Iron Ring Ceremony&lt;/strong> &amp;mdash; Last year I finished my degree, but my actual graduation and Iron Ring Ceremony happened in 2023. The Iron Ring Ceremony was particularly special because my brother was able to be a special presenter of my ring. I&amp;rsquo;m not much of a ring guy, but I plan to wear my Iron Ring for the rest of my life.&lt;/li>
&lt;li>&lt;strong>Spending time with family&lt;/strong> &amp;mdash; I&amp;rsquo;m really thankful I decided to stay in Edmonton so I could be around my family. I love spending time with the family and I love watching my niblings grow up. I cherish the countless dinners and outings over the year.&lt;/li>
&lt;li>&lt;strong>Playing hockey&lt;/strong> &amp;mdash; Go Powder Puffs go! I&amp;rsquo;ve been lucky to fall in with a great team for beginner level adult hockey and it has been so much fun.&lt;/li>
&lt;li>&lt;strong>Playing D&amp;amp;D&lt;/strong> &amp;mdash; Thanks to serendipity I ended up playing D&amp;amp;D in a group ran by my grade 5 teacher. It was not only a blast playing D&amp;amp;D with my friends, but it was also great reconnecting with my teacher.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to my &lt;strong>Grand Prairie trip&lt;/strong>, my &lt;strong>Seattle trip&lt;/strong>, &lt;strong>jamming&lt;/strong> on guitar with friends, and all the &lt;strong>hockey games&lt;/strong> and &lt;strong>concerts&lt;/strong> I went to.&lt;/p>
&lt;h2 id="top-10-movies-and-tv-shows">Top 10 Movies and TV Shows&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Daisy Jones and the Six&lt;/strong> &amp;mdash; As frustrating as the character of Daisy can be I still loved this show. I binged this one so hard!&lt;/li>
&lt;li>&lt;strong>Succession&lt;/strong> &amp;mdash; The final season of Succession really delivered. It offered up some of the most visceral acting I&amp;rsquo;ve ever seen.&lt;/li>
&lt;li>&lt;strong>The Ballad of Buster Scruggs&lt;/strong> &amp;mdash; A compilation of stories that make you laugh and cry. By far the best story telling I saw all year.&lt;/li>
&lt;li>&lt;strong>Get Out&lt;/strong> &amp;mdash; This came out quite awhile ago, but stood up to all the hype and accolades it has received.&lt;/li>
&lt;li>&lt;strong>Dungeons &amp;amp; Dragons: Honor Among Thieves&lt;/strong> &amp;mdash; This was definitely a box office style movie, but Chris Pine is one my favorite actors and I really appreciated all the inside jokes for people who play D&amp;amp;D.&lt;/li>
&lt;li>&lt;strong>Love, Death &amp;amp; Robots&lt;/strong> &amp;mdash; This show takes on a very unique format. Some of the shorts were amazing, and some were gross or uncomfortable. My favorites were &amp;ldquo;When the Yogurt Took Over&amp;rdquo; and &amp;ldquo;Ice Age&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Thirteen Lives&lt;/strong> &amp;mdash; I may never dive again after watching this movie. It is absolutely wild what those divers did for those boys.&lt;/li>
&lt;li>&lt;strong>Better Call Saul&lt;/strong> &amp;mdash; Overall I think Breaking Bad was better, but this let me binge a little longer.&lt;/li>
&lt;li>&lt;strong>Marvelous Mrs. Maisel&lt;/strong> &amp;mdash; A great show that regrettably starts to become a little all over the place in later seasons.&lt;/li>
&lt;li>&lt;strong>The Bear&lt;/strong> &amp;mdash; I&amp;rsquo;d heard so much about this show that I had to watch it. I loved the window into restaurant culture and I had also just come back from Chicago so it was cool to recognize locations. Not so sure I loved the yelling though. I also thought the second season was a flop.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to &lt;strong>Barbenheimer&lt;/strong>, &lt;strong>Napolean&lt;/strong>, &lt;strong>I Think You Should Leave&lt;/strong>, &lt;strong>Shane Gillis: Beautiful Dogs&lt;/strong>, &lt;strong>Pete Holmes: I Am Not For Everyone&lt;/strong>, &lt;strong>Rick and Morty&lt;/strong>, &lt;strong>The Heights&lt;/strong>, &lt;strong>The Devil Wears Prada&lt;/strong>, &lt;strong>Straight Outta Compton&lt;/strong>, and episode 3 of &lt;strong>The Last of Us&lt;/strong>. I watched so many good shows this year that I couldn&amp;rsquo;t help but mention them.&lt;/p>
&lt;h2 id="top-10-books">Top 10 Books&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>The Problem of God by Mark Clark&lt;/strong> &amp;mdash; It reminds me of a modernized version of The Case For Christ. This was the book that made me realize that intellectually I was a Christian.&lt;/li>
&lt;li>&lt;strong>Crashing Through by Robert Kurson&lt;/strong> &amp;mdash; A fantastic story about pushing through adversity. I love Robert Kurson&amp;rsquo;s writing.&lt;/li>
&lt;li>&lt;strong>Mere Christianity by C.S. Lewis&lt;/strong> &amp;mdash; I through this along with a study guide which I found very helpful. I&amp;rsquo;ve been planning to read this book for more than five years so it felt good to finally do it. I definitely felt the first half of the book was better than the second half.&lt;/li>
&lt;li>&lt;strong>The Butcher of Anderson Station by Daniel Abraham&lt;/strong> &amp;mdash; I really liked the format of a shorter story that fleshes out the background of a longer series.&lt;/li>
&lt;li>&lt;strong>Caliban&amp;rsquo;s War by James S. A. Corey&lt;/strong> &amp;mdash; I don&amp;rsquo;t want to give anything away, but I think The Expanse is great and totally worth the read.&lt;/li>
&lt;li>&lt;strong>Abaddon&amp;rsquo;s Gate by James S. A. Corey&lt;/strong> &amp;mdash; Like I said above, The Expanse is great.&lt;/li>
&lt;li>&lt;strong>?&lt;/strong>&lt;/li>
&lt;li>&lt;strong>?&lt;/strong>&lt;/li>
&lt;li>&lt;strong>?&lt;/strong>&lt;/li>
&lt;li>&lt;strong>?&lt;/strong>&lt;/li>
&lt;/ol>
&lt;p>&lt;a href="#reading">Oops&lt;/a>.&lt;/p>
&lt;h2 id="top-10-others">Top 10 Others&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Cube&lt;/strong> &amp;mdash; In 2022 I rekindled a love of Magic: The Gathering. My favorite way to play it was a format called limited where you have to draft the deck you play. This year I got into a variation of limited called cube. In cube you draft cards from a specially curated list rather than the current Magic set. This allows you to play with cards from throughout Magic&amp;rsquo;s history at various levels of power. I was very lucky to meet a group of guys who regularly cube and I&amp;rsquo;ve been having a blast playing with them.&lt;/li>
&lt;li>&lt;strong>Lucky Paper Radio podcast&lt;/strong> &amp;mdash; I spent 4 days and 1 hour of 2023 listening to this podcast. It definitely helped fuel my cube addiction.&lt;/li>
&lt;li>&lt;strong>Acquired podcast&lt;/strong> &amp;mdash; This show has ignited a love for business in me. I love how long their episodes are.&lt;/li>
&lt;li>&lt;strong>Game Changer YouTube Shorts&lt;/strong> &amp;mdash; Far and away the best trio is Zac, Brennan, and Josh.&lt;/li>
&lt;li>&lt;strong>Lords of Limited podcast&lt;/strong> &amp;mdash; Lords of Limited has definitely taken the place of Limited Resources as my go to podcast in the MTG limited scene. I think they have better banter and hotter takes.&lt;/li>
&lt;li>&lt;strong>Ashe&lt;/strong> &amp;mdash; I discovered Ashe this year and couldn&amp;rsquo;t get enough of her. I saved 26 of her songs on Spotify.&lt;/li>
&lt;li>&lt;strong>Lex Fridman podcast&lt;/strong> &amp;mdash; I&amp;rsquo;m less into this podcast then I used to be, but Lex still delivers on high quality long-form interviews across a broad spectrum of people.&lt;/li>
&lt;li>&lt;strong>Joseph&lt;/strong> &amp;mdash; Joseph is the other artist I couldn&amp;rsquo;t get out of my head this year.&lt;/li>
&lt;li>&lt;strong>Yoga with Adriene&lt;/strong> &amp;mdash; At first I found Adriene&amp;rsquo;s vibe off-putting, but now I love it. She is such a positive force.&lt;/li>
&lt;li>&lt;strong>The Ultimate GarageBand Beginner&amp;rsquo;s Guide&lt;/strong> &amp;mdash; This YouTube playlist is an amazing free resource. Not only did it teach me the mechanics of using GarageBand, but it also taught me the basic principles of recording, mixing, and mastering.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to &lt;strong>Harry Styles&lt;/strong>, &lt;strong>Tor Miller&lt;/strong>, &lt;strong>Suki Waterhouse&lt;/strong>, &lt;strong>Stay Sassy&lt;/strong>, &lt;strong>MTG Arena&lt;/strong>, and &lt;strong>blog fan mail&lt;/strong>.&lt;/p>
&lt;h2 id="reflecting-on-2023">Reflecting on 2023&lt;/h2>
&lt;h3 id="career">Career&lt;/h3>
&lt;blockquote>
&lt;p>After nearly six years of university and internships I feel that I&amp;rsquo;ve learned how to excel in these domains. I&amp;rsquo;m now entering into a whole new world of full-time work. I&amp;rsquo;m going to need to learn a lot of new skills and lessons to start mastering this domain.&lt;/p>&lt;/blockquote>
&lt;p>I&amp;rsquo;m just about at one year of working at Fermyon and it has been a treat. The work is interesting; the team is super smart and friendly; I get plenty of travel opportunities and tech conference exposure; I&amp;rsquo;m excited about the potential for the company; and they&amp;rsquo;re very supportive of a healthy work-life balance. Perhaps the biggest highlight of my career in 2023 was the opportunity I had to give my first ever conference talk at &lt;a href="https://www.youtube.com/watch?v=WBxlLMeyE7I">Cloud Native Rejekts&lt;/a>. My talk was a beginner-level explanation of WebAssembly.&lt;/p>
&lt;p>My goal of writing a blog post was too specific of a goal and ended up being important to me. I also think I did a poor job of networking in Edmonton — read as I didn&amp;rsquo;t network in Edmonton. That being said I built more of network in the cloud native and Wasm ecosystems this year than I expected to thanks to all the travel.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Find and maintain a sustainable work-life balance.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Actively seek 1-1&amp;rsquo;s to learn and build relationships with teammates.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Write a blog post in January detailing my plan and goals for my new job more thoroughly.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Start building a professional network in Edmonton.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Go to 2+ technical conferences.&lt;/li>
&lt;/ul>
&lt;h3 id="finances">Finances&lt;/h3>
&lt;blockquote>
&lt;p>Thanks to hard work, networking, and some luck I&amp;rsquo;ve found myself in a position where I&amp;rsquo;m going to be making more money than I&amp;rsquo;m used to. Given this opportunity I want to take maximum advantage of it and save as much as I can. Savings now are going to compound a lot more than savings I accrue when I&amp;rsquo;m older. Not to mention the fact that I&amp;rsquo;m single and have no dependents which provides me with lots of flexibility in how I spend my money.
&lt;br>&lt;br>
So given this sudden increase in income and desire to save a large percentage of it, I have decided I need to strap some training wheels on for the year. In other words I&amp;rsquo;ve decided that I need to start monthly budgeting again. My hope is the monthly budget will allow me to save significantly while also spending my money on fun things guilt free.
&lt;br>&lt;br>
How I put this money I save to work is a more complicated question that I plan to answer throughout the year. There are many things I can do with it: pay off my debt; build a sizeable emergency fund; save for travel in the mid-term; invest for retirement in the long-term. The ordering and weighting of these uses is to be determined.&lt;/p>&lt;/blockquote>
&lt;p>By far the biggest financial win of the year was that I used YNAB religiously. It was the training wheels I needed &amp;mdash; and the training wheels worked. Reconciling my finances weekly was game changing. I ended up deprecating my high level spreadsheet because YNAB was filling enough of the same role that it felt redundant to do both.&lt;/p>
&lt;p>My saving and donating goals were very ambitious and I did better than I expected to in meeting them. I was very good at immediately setting the money aside from my paycheck in the right amounts to hit my goals. But because I wasn&amp;rsquo;t using automatic deposits to investments/charities I was able to occasionally cheat by dipping into these piles to cover other shortfalls in my budget. In the end I donated ~9% of my net income (~6% gross income) and I saved ~27% of my net income (~19% gross income). Note that the vast majority of my saving (~57%) was deployed to paying off my debt. Thanks to which I&amp;rsquo;m debt free now!&lt;/p>
&lt;p>I have two takeaways from the above calculations: First, I need to setup automatic deposits. Even with a budgeting app it is too easy to cheat and move that money around. Second, I need to be a little less ambitious in my goals. More realistic goals will allow me to actually strive to meet them.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Use a monthly budget like &lt;a href="https://ynab.com">YNAB&lt;/a> all year.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep my high level spreadsheet updated.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Donate 10% of my gross income.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Save a minimum of 15% of my gross income.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Research investment strategies outside of RRSP.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Save 30% of my gross income.&lt;/li>
&lt;/ul>
&lt;h3 id="physical-health">Physical Health&lt;/h3>
&lt;blockquote>
&lt;p>This year I&amp;rsquo;ve split physical and mental health into separate categories. While they are tightly linked in how the affect each other, I think it is helpful to view my goals for them as separate.
&lt;br>&lt;br>
The main thrust of my physical health goals is to continue with Stronglifts 5x5. I&amp;rsquo;ve set my year end goal based on the &lt;a href="https://stronglifts.com/5x5/#Strength_Goals">Intermediate I&lt;/a> goals found on the website translated to 5 rep maxes. The secondary thrust is to continue to build a resilient body through things like yoga and stretching so that I get injured less.&lt;/p>&lt;/blockquote>
&lt;div class="begin-task-list">&lt;/div>
&lt;p>Overall I felt 2023 was an average to below average year for my physical health. I did yoga in January and played lots of hockey along with successfully rehabbing a concussion. But I only managed to do Stronglifts for four and half months of the year. Working out five times a week was also a wildly over-ambitious goal. And with all the travel I varied wildly each month how much I was working out. There was also a very tough end to the year which involved me tearing my MCL while playing hockey.&lt;/p>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Do yoga every day in January.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Play a sport (hockey) once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Workout (yoga/weightlifting/etc.) three times a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Actually warmup, cooldown, and stretch when I workout.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 5RM of: squat 255 lbs, deadlift 355 lbs, bench press 175 lbs, barbell row 155 lbs, and overhead press 90 lbs.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Workout (yoga/weightlifting/etc.) five times a week.&lt;/li>
&lt;/ul>
&lt;h3 id="mental-health">Mental Health&lt;/h3>
&lt;blockquote>
&lt;p>My mental health has a huge impact on my day to day life so I want to continue to actively work to improve it. In a previous &lt;a href="/blog/2022/inception-studio-weekend-reflections/#the-value-of-regular-therapy-or-life-coaching">post&lt;/a> I talked about how I think more regular therapy could be valuable. I want to try and enact that this year.&lt;/p>&lt;/blockquote>
&lt;p>From the perspective of how my mental health was over the last year I would say 2023 was a success. I certainly had lots of low moments, but on the whole I did a good job of managing them and my emotions around them. In terms of my specific goals I think I did a poor job. I basically enacted none of my plan. On one hand it is a positive sign that the year still went well regardless. But on the other hand I&amp;rsquo;d like to stick with the plan.&lt;/p>
&lt;p>In particular I think my relationship with my phone is the most worrying and difficult to address element of my mental health. I&amp;rsquo;m not a fan of how central of a role my phone takes in my life. Worse, the things I&amp;rsquo;ve tried to do to address it haven&amp;rsquo;t worked. It seems like a going cold turkey is the best bet, but I&amp;rsquo;m also not ready to do that.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build life coaching or therapy into my life on a regular cadence.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep using yoga as a tool to aid in my mental health.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Avoid having my phone be the first thing I interact with in the morning.&lt;/li>
&lt;/ul>
&lt;h3 id="relationships">Relationships&lt;/h3>
&lt;blockquote>
&lt;p>This is a new category I&amp;rsquo;m adding this year. It has become abundantly clear to me over the past year how good relationships are one of the most important factors to my happiness. Given that I&amp;rsquo;ve graduated university and decided to work for a remote company I am going to need to be extremely proactive about finding, building, and maintaining relationships.&lt;/p>&lt;/blockquote>
&lt;p>I think 2023 was very successful on the relationship front. I spent a lot of time with my family which I loved and notably I met someone. While I didn&amp;rsquo;t execute on my goals with as much gusto as I thought I would I still think I met the bar for each of the goals.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Plan semi-regular lunches with friends and family in town as a way to get out of the house.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Get involved in 1-2 activities where I can meet people e.g. hockey team, dance lessons, etc.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Host friends and family more often at my place.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Be proactive about setting up calls with friends who live far away.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure">Travel and Adventure&lt;/h3>
&lt;blockquote>
&lt;p>Truth be told I&amp;rsquo;m not sure the best way to set goals in this area of my life. If I&amp;rsquo;m too specific than I always miss the goals because plans change. But, if I&amp;rsquo;m too generic I don&amp;rsquo; think the goals are in any way valuable. For this year what I&amp;rsquo;ll try doing is listing some adventures that I&amp;rsquo;m entirely confident I want to do. I&amp;rsquo;ll also include some stretch goals of things that sound fun and that I want to look into, but that I&amp;rsquo;m not ready to fully commit to.&lt;/p>&lt;/blockquote>
&lt;p>While I only hit two of my specific goals I still think I had a very successful year of travel. My trips to Scandinavia and Peru really standout. Thanks to work I was able to travel to a number of places too: Seattle, Chicago, Cancun, etc. I counted and I was on 30 flights throughout the year. A lot more than I expected. My goal for doing an imperial century definitely should have been under my physical health section.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Visit friends in Seattle.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Bike an imperial century.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Do a multi-night canoe trip on the North Saskatchewan.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Join a digital nomad group like Remote Year.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Plan a solo-trip to another country I haven&amp;rsquo;t been to.&lt;/li>
&lt;/ul>
&lt;h3 id="building">Building&lt;/h3>
&lt;blockquote>
&lt;p>This is one of the few categories where I&amp;rsquo;m setting a less ambitious goal. I&amp;rsquo;m very excited about building cool things and I think that long term it has compounding effects. However, now that I&amp;rsquo;m graduating I feel that I&amp;rsquo;ve entered a time in my life where I&amp;rsquo;m more focused on finding balance in my life as opposed to obsession. Put another way, I feel less of desire to grind on side projects than I did a year ago. I&amp;rsquo;m sure this desire will ebb and flow with time.
&lt;br>&lt;br>
Although I&amp;rsquo;m only aiming for one project I&amp;rsquo;m sure it will take me a couple tries to find an idea that I&amp;rsquo;m really excited to hack on. I also want to highlight the importance of the publishing and writing about it steps. I&amp;rsquo;m not going to consider a project done unless I do those too.&lt;/p>&lt;/blockquote>
&lt;p>I missed on both counts here. While I did build and write about some small projects on the Fermyon blog I wouldn&amp;rsquo;t count those as meaningful (in the context of my own goals). I also did nothing around a hackathon with friends. That definitely should have been a stretch goal.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build, publish, and write about, one meaningful project.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Organize a low-key weekend hackathon with my friends.&lt;/li>
&lt;/ul>
&lt;h3 id="reading">Reading&lt;/h3>
&lt;blockquote>
&lt;p>Nothing crazy here. I want to continue to build the habit of reading books because I think it is a good counter-measure against the rapid pace of our digital world. In terms of genres I&amp;rsquo;m open to reading any mix of fiction, non-fiction, and self-help.&lt;/p>&lt;/blockquote>
&lt;p>This was a really big miss. While the books I read this year were very impactful I didn&amp;rsquo;t read nearly as much as I wanted to. Oh well, next year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 1.5+ books a month (for a total of 18+ over the year).&lt;/li>
&lt;/ul>
&lt;h3 id="writing">Writing&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;m really pleased with how my blogging went last year and all I&amp;rsquo;m aiming to do in 2023 is keep a similar cadence. I could set a really aggressive stretch goal here, but I don&amp;rsquo;t think I actually desire to write that much. My perspective is that about one post a month will still give me the benefits I&amp;rsquo;m looking for. I&amp;rsquo;m also not going to set any goals about having posts go viral &amp;mdash; that either happens or it doesn&amp;rsquo;t.
&lt;br>&lt;br>
The only thing I would change about my writing last year was that I wish it was more consistent. I think it is better to write one post a month rather than four posts one month and then zero the next. Consistency will not only help me build discipline in the practice of writing, but it will also build my credibility and audience.&lt;/p>&lt;/blockquote>
&lt;p>My writing was not as much of a failure as my reading was, but it still wasn&amp;rsquo;t great. I definitely was not consistent like I hoped I would be. Including this post I wrote 8 posts this year. Three of those were done on company hours for the company blog. And it&amp;rsquo;s very generous to call my &lt;a href="/blog/2023/webassembly-reading-list/">WebAssembly reading list&lt;/a> a blog post &amp;mdash; it really was more of an accompaniment to my conference talk. I will give myself a small pat on the back though for at least blogging a few times and not letting it go completely.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 12+ blog posts.&lt;/li>
&lt;/ul>
&lt;h3 id="music">Music&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;m so pleased that two years ago I decided to pick up guitar. Doing so filled a hole in my life that I wasn&amp;rsquo;t aware of &amp;mdash; making music. As a kid music was a big part of my life, but it faded in university. Suffice to say I&amp;rsquo;m glad it&amp;rsquo;s back. Music is a more apt name for this category because my interests are starting to wander beyond guitar. In addition to my goals of progressing on guitar I&amp;rsquo;m also starting to have the desire to pick up piano too. I&amp;rsquo;m leaving it as a stretch goal and we&amp;rsquo;ll see where the year takes me.&lt;/p>&lt;/blockquote>
&lt;p>Last year was a smashing success on the music front. I wasn&amp;rsquo;t very good at intentional practice on the guitar or making progress through Justin Guitar. But, I did knock it out of the park on other fronts: I jammed with friends a lot; learned the Nashville number system and started songwriting; recorded, mixed, and mastered an original song; led a group of carolers in an impromptu performance at a seniors center; played in worship bands multiple times; and even started publicly singing in worship bands. I&amp;rsquo;m thankful for all the work I put into guitar so that I could reap all these benefits.&lt;/p>
&lt;p>Last year I also acquired my first acoustic guitar &amp;ldquo;Luther&amp;rdquo;, a D-16e Martin. Towards the end of the year I also purchased &amp;ldquo;Miller&amp;rdquo;, a Roland FP-30X digital piano, to assist with my piano lessons that start in 2024.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Grade 3 and finish 3 modules in Justin Guitar Grade 4.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Ear Training and Theory up to Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Learn to use a looper with &lt;a href="https://learnpracticeplay.com/loop-school-signup/">this&lt;/a> course.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep playing in front of family and friends to practice performing.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep jamming with other guitarists.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Stretch goal: Record a cover of a song.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Start taking piano lessons.&lt;/li>
&lt;/ul>
&lt;h2 id="looking-forward-to-2024">Looking Forward to 2024&lt;/h2>
&lt;h3 id="career-1">Career&lt;/h3>
&lt;p>I&amp;rsquo;m very happy at Fermyon so I want to focus on keeping a good thing good. The biggest priority here is keeping a health work-life balance so I don&amp;rsquo;t get burnt out like I did in university. I also want to up my game on the 1-1 front. Last year I did a good job of doing 1-1&amp;rsquo;s with everyone on the team, but then I quickly lost steam. I want to build more regular 1-1&amp;rsquo;s into my schedule because improving relationships will help me do better work at Fermyon and open up job opportunities in the future. Not to mention the benefit of socializing in an often isolating remote job. Finally, I want to learn to love the problems were solving for customers and not the solution we&amp;rsquo;ve created. Put another way I want to focus on writing high impact code that matters.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Maintain a sustainable work-life balance.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Build a more rigorous schedule of 1-1&amp;rsquo;s for learning, mentoring, and socializing.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Give another conference talk.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Start building a professional network in Edmonton.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Try out a co-working space.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Find a way to contribute to the upstream Wasm ecosystem.&lt;/li>
&lt;/ul>
&lt;h3 id="finances-1">Finances&lt;/h3>
&lt;p>Last year was a financial success so I plan to keep it simple and stay the course. To make sure I hit my saving/donating goals I&amp;rsquo;m going to setup automatic deposits. I also want to spend some time at the start of the year to tweak how I&amp;rsquo;m using YNAB to be more effective. As a stretch goal it&amp;rsquo;d be good to find a financial advisor who can help me with my complex situation of dual citizenship. Also depending on how things go throughout the year I might look into buying a home with a rental basement suite.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Use &lt;a href="https://ynab.com">YNAB&lt;/a> all year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Save 20% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Donate 10% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Find a financial advisor.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Buy a home.&lt;/li>
&lt;/ul>
&lt;h3 id="physical-health-1">Physical Health&lt;/h3>
&lt;p>Most of my focus in the start of the year will be around rehabbing my knee. In terms of exercise I&amp;rsquo;m setting a more realistic goal of twice a week at a minimum. I&amp;rsquo;m also setting the goal of being able to do a pull-up because I&amp;rsquo;ve always wanted to be able to do one.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Rehab my knee injury.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Be able to do a pull-up by the end of the year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do yoga every day in January.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do a meaningful workout at least twice a week (yoga at studio/strength training/go for a run or bike/etc.)&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Play a sport once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Bike an imperial century.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Train for a try-a-triathlon.&lt;/li>
&lt;/ul>
&lt;h3 id="mental-health-1">Mental Health&lt;/h3>
&lt;p>My main goal for mental health in this coming year is to build a more healthy relationship with my phone and social media. I don&amp;rsquo;t think the goals I&amp;rsquo;ve listed are sufficient for a perfectly healthy relationship, but I do think they are achievable and a good starting point.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Pick a few Sundays to stay off my phone.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Don&amp;rsquo;t use my phone between 9pm and 9am.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Have a &lt;a href="https://allenpike.com/2023/have-a-research-question">research question&lt;/a> about how productive people relate to their phones and social media.&lt;/li>
&lt;/ul>
&lt;h3 id="relationships-1">Relationships&lt;/h3>
&lt;p>My relationship goals this year are mostly focused on maintenance with some opportunities on the side to grow my circle of relationships.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Prioritize and maintain relationships with my girlfriend, family, and close friends.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Schedule regular lunches with friends and family in town.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep in touch with friends abroad.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Host six dinner parties.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Meet more local people in the music/tech/Christian scenes.&lt;/li>
&lt;/ul>
&lt;h3 id="faith">Faith&lt;/h3>
&lt;p>Coming to faith last year was a big change. I want to focus on approaching it with intentionality in 2024. Finding a community to get plugged into is going to be critical.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Find a church home.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Get plugged into a small group at Church.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Build a habit of reading the Bible and praying three mornings a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Read 8 books of the Bible.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Pick a few Sundays to try sabbathing.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure-1">Travel and Adventure&lt;/h3>
&lt;p>For 2024 I&amp;rsquo;m going to list a bunch of things that I potentially could do, but go in with my eyes open and make them all stretch goals. Hopefully this list will give me inspiration throughout the year, but will also give me the space to be spontaneous.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Family canoeing trip in Bowron Lake Provincial Park.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Go to New York.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Take dance lessons.&lt;/li>
&lt;/ul>
&lt;h3 id="organization">Organization&lt;/h3>
&lt;p>I felt like I needed a new section to track my progress around productivity, organization, and digital tools. I really want to find a note taking tool I can consolidate on like I did for task management with Todoist. Right now I just use a mix of random tools that aren&amp;rsquo;t working well for me.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Keep using Todoist for task management.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Consolidate on a note taking tool like Obsidian.&lt;/li>
&lt;/ul>
&lt;h3 id="building-1">Building&lt;/h3>
&lt;p>Building things is still exciting to me and perhaps more importantly is a compounding activity so I&amp;rsquo;m keeping this goal around in a very simple form. I would hope that the project either is deeply technical or solves a problem. In the case of a deeply technical project it will act as a learning/teaching opportunity. If it solves a problem then it will help me flex my entrepreneurial muscles. I&amp;rsquo;ve moved the hackathon to a stretch goal because I still think it is a great idea, but it would take some meaningful work.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build, publish, and write about, one meaningful project.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Organize a low-key weekend-long remote hackathon with my friends.&lt;/li>
&lt;/ul>
&lt;h3 id="reading-1">Reading&lt;/h3>
&lt;p>Based on how little I read last year I&amp;rsquo;m toning this goal back a little bit to make it more reasonable. One potential issue that occurs to me is that if I only read 12 books that&amp;rsquo;s still not a lot to construct a top 10 from. That can be next years problem.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 1+ books a month (for a total of 12+ over the year).&lt;/li>
&lt;/ul>
&lt;h3 id="writing-1">Writing&lt;/h3>
&lt;p>I&amp;rsquo;m going to keep this goal the same as last year. It&amp;rsquo;s worth noting that ideally I publish about once a month to practice my consistency.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 12+ blog posts.&lt;/li>
&lt;/ul>
&lt;h3 id="music-1">Music&lt;/h3>
&lt;p>My most extensive list of goals for 2024 is here in the music section. I just have so much that I&amp;rsquo;m excited to do. The headline here is that I&amp;rsquo;m starting to take piano lessons. I&amp;rsquo;m stoked to get some formal training and plug the theory and ear training gaps that I have. My hope is that while I devote a lot of my energy to piano I&amp;rsquo;ll still have enough to make a modicum of progress on guitar. We&amp;rsquo;ll see how that goes.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Take weekly piano lessons all year and perform in recitals.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Practice piano 30 minutes a day.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Jam with musical friends often.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Continue to play publicly in worship bands.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Write four songs.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Release two mixed and mastered songs on SoundCloud.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Pass Royal Conservatory Piano level 1 exam.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Complete Justin Guitar Ear Training Grade 3 and Justin Guitar Theory Grade 5.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Organize and perform a small concert at a senior center.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Learn to use a looper with &lt;a href="https://learnpracticeplay.com/loop-school-signup/">this&lt;/a> course.&lt;/li>
&lt;/ul>
&lt;h2 id="closing-thoughts">Closing Thoughts&lt;/h2>
&lt;p>Reflecting on 2023 fills my heart with gratitude for the things I was able to do and the people I did it with. Mulling over my goals for 2024 has me excited for the year to come. As I dive into 2024 I want to acknowledge the seasonality of life and encourage myself to tackle goals as I&amp;rsquo;m excited about them. I don&amp;rsquo;t always have to be pushing each boulder up the hill. Sometimes it&amp;rsquo;s okay to pick the one that feels right for that season of life. If you got to the bottom of this thanks for reading and see you in 2024.&lt;/p></description></item><item><title>WebAssembly Reading List</title><link>/blog/2023/webassembly-reading-list/</link><pubDate>Fri, 27 Oct 2023 00:00:00 +0000</pubDate><guid>/blog/2023/webassembly-reading-list/</guid><description>&lt;p>I&amp;rsquo;m giving a talk titled &amp;ldquo;What Actually Is WebAssembly&amp;rdquo; at Cloud Native Rejekts 2023. As part of my talk I want to help people further explore the world of WebAssembly. This is a list of blog posts, documentation, and videos that I think might be interesting to someone who is learning about WebAssembly. I&amp;rsquo;ll do my best to keep this up to date. Without further ado here is my WebAssembly reading list.&lt;/p>
&lt;h3 id="mozilla-developer-network-webassembly-docs">&lt;a href="https://developer.mozilla.org/en-US/docs/WebAssembly">Mozilla Developer Network WebAssembly Docs&lt;/a>&lt;/h3>
&lt;p>These are the gold standard of entry-level WebAssembly documentation. It&amp;rsquo;s particularly useful if you&amp;rsquo;re interested in using Wasm in the browser or if you&amp;rsquo;re interested in learning about the WebAssembly text format in depth.&lt;/p>
&lt;h3 id="standardizing-wasi-a-system-interface-to-run-webassembly-outside-the-web">&lt;a href="https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/">Standardizing WASI: A system interface to run WebAssembly outside the web&lt;/a>&lt;/h3>
&lt;p>WASI is exciting because it is bringing so much functionality to WebAssembly. This blog post is a great introduction to WASI and why it is important. If you like Lin Clark&amp;rsquo;s comic style explainers I recommend you check out all of &lt;a href="https://code-cartoons.com/">her work&lt;/a>.&lt;/p>
&lt;h3 id="luke-wagner-on-the-webassembly-component-model">&lt;a href="https://www.youtube.com/watch?v=tAACYA1Mwv4">Luke Wagner on the WebAssembly Component Model&lt;/a>&lt;/h3>
&lt;p>Luke Wagner gave a great talk at WasmCon 2023 about what the WebAssembly component model is. He&amp;rsquo;s a fantastic speaker and the component model has the potential to rethink how we write software.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/tAACYA1Mwv4?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video">&lt;/iframe>
&lt;/div>
&lt;h3 id="the-webassembly-specification">&lt;a href="https://webassembly.github.io/spec/core/">The WebAssembly Specification&lt;/a>&lt;/h3>
&lt;p>Don&amp;rsquo;t let the fact that this is a technical specification scare you. It is really well written and quite a short read if you skip all the formal notation. There&amp;rsquo;s nothing quite like going to the primary source.&lt;/p>
&lt;h3 id="fermyon-guide-to-webassembly-languages">&lt;a href="https://www.fermyon.com/wasm-languages/webassembly-language-support">Fermyon Guide to WebAssembly Languages&lt;/a>&lt;/h3>
&lt;p>Fermyon&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> maintains a fantastic resource outlining support for WebAssembly across the top 20 programming languages.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Full disclosure I work at Fermyon.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>How I Built an AI Inferencing API With Llama2 on Spin</title><link>/blog/2023/how-i-built-an-ai-inferencing-api-with-llama2-on-spin/</link><pubDate>Thu, 21 Sep 2023 00:00:00 +0000</pubDate><guid>/blog/2023/how-i-built-an-ai-inferencing-api-with-llama2-on-spin/</guid><description>&lt;p>If this fails to redirect automatically you can find the post &lt;a href="https://www.fermyon.com/blog/how-i-built-an-ai-inferencing-api-with-llama2-on-spin">here&lt;/a>.&lt;/p></description></item><item><title>Debugging With the Key/Value Store Explorer</title><link>/blog/2023/debugging-with-the-key/value-store-explorer/</link><pubDate>Thu, 27 Apr 2023 00:00:00 +0000</pubDate><guid>/blog/2023/debugging-with-the-key/value-store-explorer/</guid><description>&lt;p>If this fails to redirect automatically you can find the post &lt;a href="https://www.fermyon.com/blog/debugging-with-the-key-value-store-explorer">here&lt;/a>.&lt;/p></description></item><item><title>How I Built a Like Button for My Blog with Spin</title><link>/blog/2023/how-i-built-a-like-button-for-my-blog-with-spin/</link><pubDate>Wed, 26 Apr 2023 00:00:00 +0000</pubDate><guid>/blog/2023/how-i-built-a-like-button-for-my-blog-with-spin/</guid><description>&lt;p>If this fails to redirect automatically you can find the post &lt;a href="https://www.fermyon.com/blog/how-i-built-a-like-button-for-my-blog-with-spin">here&lt;/a>.&lt;/p></description></item><item><title>Tackling My Overflowing Reading List</title><link>/blog/2023/tackling-my-overflowing-reading-list/</link><pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate><guid>/blog/2023/tackling-my-overflowing-reading-list/</guid><description>&lt;p>Do you ever find yourself opening endless tabs on your phone&amp;rsquo;s browser with posts you want to read, but never actually get around to reading them? Recently, I discovered a technique to help me manage my ever-growing reading list, and I thought it would be worth sharing.&lt;/p>
&lt;p>Whenever I come across a page I think I would enjoy reading I open it as a new tab in my phone&amp;rsquo;s browser. Between email newsletters and my RSS feed it&amp;rsquo;s not uncommon for me to store away multiple posts for later reading in a day. It wouldn&amp;rsquo;t take long before I had twenty to thirty tabs open each containing a page I was genuinely interested in but never seemed to have the time to read. It started to feel overwhelming, and I knew I needed a solution.&lt;/p>
&lt;p>That&amp;rsquo;s when I discovered my phone&amp;rsquo;s text-to-speech feature. Most smartphones have a built-in ability to read webpages aloud, and it&amp;rsquo;s been the key to helping me &amp;ldquo;read&amp;rdquo; through my backlog. On a Pixel 7 all you have to do is open the webpage and then say &amp;ldquo;Hey Google! Read this to me.&amp;rdquo;&lt;/p>
&lt;p>Now, whenever I have some free time, like when I&amp;rsquo;m walking or doing chores, I let my phone read the posts out loud. Doing this has essentially turned these unread posts into a personalized podcast library. I can even turn up the reading speed to 1.5x which really lets me power through the backlog.&lt;/p>
&lt;p>While this technique has been immensely helpful for me, it&amp;rsquo;s worth noting that it has a couple of disadvantages. First, the text-to-speech voice can be very monotonous. Second, this approach works poorly for highly technical content or posts that rely heavily on visuals.&lt;/p>
&lt;p>Hope this helps anybody else out there with an overwhelming reading list.&lt;/p></description></item><item><title>Learning to Play Hockey as an Adult</title><link>/blog/2023/learning-to-play-hockey-as-an-adult/</link><pubDate>Tue, 28 Feb 2023 00:00:00 +0000</pubDate><guid>/blog/2023/learning-to-play-hockey-as-an-adult/</guid><description>&lt;p>A little more than a year ago I started &lt;a href="/blog/2022/skating-my-way-to-a-new-mindset">teaching myself&lt;/a> to skate at the outdoor rink near my house. Five months ago I enrolled in a beginner hockey program for adults called &lt;a href="https://www.discoverhockey.com/">Discover Hockey&lt;/a>. Two months ago I started playing on my first adult hockey team. Going from not knowing how to skate to playing on a hockey team once a week has been quite the transition. I wanted to document what learning to play hockey as an adult has been like.&lt;/p>
&lt;p>Perhaps the predominant emotion I&amp;rsquo;ve felt throughout this process has been excitement. I&amp;rsquo;m not exaggerating when I say that for the first few months hockey night was the best night of my week. The rest of the week I would be day dreaming about getting back on the ice. Driving to the rink I would feel a giddiness that I hadn&amp;rsquo;t felt in years. While there has been a natural tempering of this passion, game night is still the best part of my week. Hockey is a thrilling game to watch and an even more thrilling game to play.&lt;/p>
&lt;p>I&amp;rsquo;ve played a wide swath of sports and I can say without hesitation that hockey is the most tiring of all of them. I used to snicker at the minute long shifts of hockey. I thought it paled in comparison to the 80 consecutive minutes of rugby that I was playing at the time. But, wow was I ever wrong. If you&amp;rsquo;re doing it right hockey is incredibly tiring.&lt;/p>
&lt;p>An unexpected element of the experience was the hockey culture. A hockey team (even a division 10 beer league) breeds a lot of camaraderie and teams bond very quickly. Even a hockey locker room has a certain je ne sais quoi that I was not expecting. Growing up I never understood why kids who played hockey behaved different than everyone else — now I understand where it comes from and the hockey locker room is ground zero.&lt;/p>
&lt;p>It has also been a very embarrassing experience. You have to swallow your pride to be able to waddle out on to the ice ready to fall more times than you would like. You have to be okay with the fact that most ten year olds playing hockey are better than you and are probably better than you&amp;rsquo;ll ever be. That can be tough, but thankfully everyone in the program had a really good attitude about it.&lt;/p>
&lt;p>Most of all I&amp;rsquo;ve found the experience of learning to play hockey as an adult to be gratifying. I&amp;rsquo;m proud that I stuck with it and pushed through all the consternation and embarrassment. And I&amp;rsquo;m thankful for all the new friends I&amp;rsquo;ve made. Caleb two years ago would not be able to believe what I can do on the ice now.&lt;/p>
&lt;p>What did you never learn to do but wish you could? Is it time to make that happen?&lt;/p></description></item><item><title>Questioning My Approach to Advice</title><link>/blog/2023/questioning-my-approach-to-advice/</link><pubDate>Sat, 21 Jan 2023 00:00:00 +0000</pubDate><guid>/blog/2023/questioning-my-approach-to-advice/</guid><description>&lt;p>Good advice is very difficult to give. This is because it is inherently context dependent. Advice is inextricably linked with the lived experience of the person giving the advice. No matter how well conceived or written, the value of advice is limited by its applicability to a given situation.&lt;/p>
&lt;p>Given how unlikely it is to produce advice that is helpful, what is someone like me with a blog supposed to do? I see three advice options and a fourth alternative: keep giving advice anyways; stop giving advice at all; make the advice context independent; or ask questions.&lt;/p>
&lt;p>Ignoring the problems with advice and barreling through is the easiest approach. It&amp;rsquo;s not a problem if I don&amp;rsquo;t acknowledge it 😉. More seriously, I think there is still some limited upside to this approach. While the advice you give is still going to be context dependent, if your audience is big enough then at least some people&amp;rsquo;s context will intersect with yours enough to make the advice useful. Overall though, this approach leaves a lot of equity on the table.&lt;/p>
&lt;p>Another approach that takes the easy way out is just refraining from giving advice at all. In other words sharing your experience but refusing to extrapolate any lessons from it. This approach does solve the problem of giving advice that doesn&amp;rsquo;t apply to others, but it also throws the baby out with the bath water. Drawing no lessons when I share my experience feels like a step too far.&lt;/p>
&lt;p>The final approach suggests that we should make the advice context independent. Doing this would solve the fundamental problem with most advice. One way we could do this is by making our advice more generic. In this way we increase the amount of people to whom it would apply. However, making advice generic may turn it into useless truisms. If that doesn&amp;rsquo;t work then what will?&lt;/p>
&lt;p>Might the answer be to pose questions instead of giving advice? Questions are much less brittle than advice and they adapt to any context. A question asks the reader to do a small amount of work by applying the question to their unique context. In exchange the reader is able to effectively generate the advice for themselves. Would you agree with me that if you give man advice you might help him a little, but if you ask a man a question you&amp;rsquo;ll probably help him a lot?&lt;/p></description></item><item><title>Year in Review: 2022</title><link>/blog/2022/year-in-review-2022/</link><pubDate>Sat, 31 Dec 2022 00:00:00 +0000</pubDate><guid>/blog/2022/year-in-review-2022/</guid><description>&lt;p>It&amp;rsquo;s hard to believe that 2022 is nearly over. In keeping with &lt;a href="/blog/2020/year-in-review-2020/">the&lt;/a> &lt;a href="/blog/2021/year-in-review-2021/">tradition&lt;/a>, here is my year end review. The review is composed of three parts: my top 10&amp;rsquo;s for the year; a reflection on 2022 similar to my &lt;a href="/blog/2022/2022-halfway-check-in/">halfway check-in&lt;/a>; and my goals and aspirations for the new year.&lt;/p>
&lt;p>I always find a review like this to be a joyous and worthwhile exercise. It&amp;rsquo;s easy to forget about all the things you did throughout the year. If you like what you read here, I would highly encourage you to do something similar.&lt;/p>
&lt;h2 id="top-10-experiences">Top 10 Experiences&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Graduating&lt;/strong> &amp;mdash; On December 19 I wrote the last ever final of my Computer Engineering degree. Earning this degree was probably the hardest thing I&amp;rsquo;ve ever done. I&amp;rsquo;m ready to finally graduate after five and half years, more than a hundred midterms and finals, and thousands of hours of studying and doing homework. Overall I&amp;rsquo;m incredibly proud of this achievement and very glad that I decided to stick it out and not go to industry early.&lt;/li>
&lt;li>&lt;strong>My sister&amp;rsquo;s wedding&lt;/strong> &amp;mdash; This was a very special experience because I&amp;rsquo;m very close with my sister. She was kind enough to ask me to be her man of honor which was such a privilege. It was also very exciting to welcome a new in-law to the family. And as an added bonus it was also a very fun party.&lt;/li>
&lt;li>&lt;strong>Learning to play hockey&lt;/strong> &amp;mdash; Last year I &lt;a href="/blog/2022/skating-my-way-to-a-new-mindset/">learned to skate&lt;/a> and started playing shinny. I&amp;rsquo;ve continued this progression in a big way. Not only have I been skating a lot but I also joined a learn to play hockey league for adults. It was so much fun that I was literally vibrating with excitement before each practice or game.&lt;/li>
&lt;li>&lt;strong>Summer in Seattle&lt;/strong> &amp;mdash; This summer I was down in Seattle for an internship at Microsoft on the Azure Kubernetes Service team. It was an incredible three months. There is way too much to unpack here so the best I can do is share some highlights which included: making a lot of new friends; playing DnD for the first time; a weekend trip to Portland; winning the Microsoft Strike capture the flag event; and my parents coming to visit.&lt;/li>
&lt;li>&lt;strong>Trip to California&lt;/strong> &amp;mdash; I spent a week in California at a &lt;a href="/blog/2022/inception-studio-weekend-reflections/">hackathon&lt;/a> and catching up with friends. This was the most impromptu trip I&amp;rsquo;ve ever taken and thus was memorable for the spontaneity alone.&lt;/li>
&lt;li>&lt;strong>Trip to the British Isles&lt;/strong> &amp;mdash; I spent two weeks driving across Ireland and Scotland with my brother and sister-in-law. Besides all the epic landscapes I got to see, the highlight was all the great conversation that was had.&lt;/li>
&lt;li>&lt;strong>Vacationing in Invermere&lt;/strong> &amp;mdash; The family and I spent a week at a beautiful VRBO in Invermere. We did a lot of fun things, but my absolute favorite part was all the laughter.&lt;/li>
&lt;li>&lt;strong>Winning HackED 2022&lt;/strong> &amp;mdash; In January I won the HackED 2022 hackathon with some friends. I wrote all about it &lt;a href="/blog/2022/building-studybuddy-at-hacked-2022/">here&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Trip to Grand Prairie&lt;/strong> &amp;mdash; A lot of my family lives up north in Grand Prairie. I got to spend a few days up there with my dad and grandmother which was a very special experience. In particular I enjoyed rekindling relationships with some of my aunts and uncles.&lt;/li>
&lt;li>&lt;strong>Rex Orange County Concert&lt;/strong> &amp;mdash; I ended up at this concert because of pure happenstance and made some really good friends. It poured rain the whole concert which oddly really amplified the experience and made it feel like it was just you and Rex.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions to the &lt;strong>Eagles concert&lt;/strong>, &lt;strong>Sausage Day&lt;/strong>, and &lt;strong>biking a metric century&lt;/strong>.&lt;/p>
&lt;h2 id="top-10-movies-and-tv-shows">Top 10 Movies and TV Shows&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Succession&lt;/strong> &amp;mdash; Totally lives up to all the hype. It has some of the best season ending twists I&amp;rsquo;ve ever seen.&lt;/li>
&lt;li>&lt;strong>Top Gun 2&lt;/strong> &amp;mdash; It&amp;rsquo;s really cliché to like this movie but I unashamedly do. It is the perfect action movie.&lt;/li>
&lt;li>&lt;strong>Breaking Bad&lt;/strong> &amp;mdash; I know I&amp;rsquo;m really late to the party here but it totally holds up.&lt;/li>
&lt;li>&lt;strong>Greyhound&lt;/strong> &amp;mdash; I&amp;rsquo;m an absolute sucker for a realistic WW2 movie and this fits the bill. No surprise that Tom Hanks is involved.&lt;/li>
&lt;li>&lt;strong>Randy Feltface&lt;/strong> &amp;mdash; I don&amp;rsquo;t really know what to say about Randy Feltface other than that you need to go watch &lt;a href="https://www.youtube.com/watch?v=Z4pkE3OFpkc&amp;amp;ab_channel=RandyFeltface">all&lt;/a> &lt;a href="https://www.youtube.com/watch?v=Ktb6Ksx1luY&amp;amp;ab_channel=Frenchipotle">of&lt;/a> &lt;a href="https://www.youtube.com/watch?v=YzxAv9qz79s&amp;amp;ab_channel=RandyFeltface">his&lt;/a> &lt;a href="https://www.youtube.com/watch?v=CXEcxvrrUYg&amp;amp;ab_channel=HeliumComedyStudios">specials&lt;/a> right now.&lt;/li>
&lt;li>&lt;strong>Ted Lasso&lt;/strong> &amp;mdash; Ted Lasso was a nice injection of wholesome into my year. Although, I have to say I thought season 2 was terrible and a huge disappointment.&lt;/li>
&lt;li>&lt;strong>Avatar: The Way of Water&lt;/strong> &amp;mdash; I didn&amp;rsquo;t go into this movie thinking I would like it, but I really did. Strong character development and great world building.&lt;/li>
&lt;li>&lt;strong>Barry Season 3&lt;/strong> &amp;mdash; Barry still keeps delivering an absurd mix of comedy and drama that I love.&lt;/li>
&lt;li>&lt;strong>Hitch&lt;/strong> &amp;mdash; A gold standard romantic comedy.&lt;/li>
&lt;li>&lt;strong>Vengeance&lt;/strong> &amp;mdash; I&amp;rsquo;m a big fan of B.J. Novak&amp;rsquo;s work so I knew I had to watch this. I thought it was great although I can&amp;rsquo;t say I loved the ending.&lt;/li>
&lt;/ol>
&lt;h2 id="top-10-books">Top 10 Books&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Shadow Divers by Robert Kurson&lt;/strong> &amp;mdash; I couldn&amp;rsquo;t put this book down. Kurson&amp;rsquo;s portrayal of deep sea wreck diving had me on the edge of my seat. Best of all, it&amp;rsquo;s a true story. I can&amp;rsquo;t recommend you read this enough.&lt;/li>
&lt;li>&lt;strong>Project Hail Mary by Andy Weir&lt;/strong> &amp;mdash; I&amp;rsquo;m loathe to actually say anything about this book because I think it is a much better read if you go into it cold and without an idea of what is to come. That being said, Andy Weir lives up to his reputation of delivering a fantastic character driven read that&amp;rsquo;s full of fun science.&lt;/li>
&lt;li>&lt;strong>The Martian by Andy Weir&lt;/strong> &amp;mdash; Even though this book is 11 years old and I&amp;rsquo;d already seen the movie, I&amp;rsquo;m very glad I took the time to read it. I think the book is much better than the movie because you get to fully immerse yourself in the scientific writing that Weir is so good at.&lt;/li>
&lt;li>&lt;strong>Leviathan Wakes by James S. A. Corey&lt;/strong> &amp;mdash; The Expanse series paints the picture of a sci-fi world that I always dreamed about as a kid.&lt;/li>
&lt;li>&lt;strong>Oscar And The Lady In Pink by Éric-Emmanuel Schmitt&lt;/strong> &amp;mdash; A very short read. I cried at the end of the book.&lt;/li>
&lt;li>&lt;strong>The Almanack of Naval Ravikant: A Guide to Wealth and Happiness by Eric Jorgenson&lt;/strong> &amp;mdash; This book has so much condensed wisdom that I only glean a small portion of it on each read through. My favorite part was his life formulas e.g. Happiness = Health + Wealth + Good Relationships.&lt;/li>
&lt;li>&lt;strong>Anthropocene Reviewed by John Green&lt;/strong> &amp;mdash; I was a long time listener to the Anthropocene Reviewed podcast so it was fun to consume it in written form.&lt;/li>
&lt;li>&lt;strong>What If? Serious Scientific Answers to Absurd Hypothetical Questions by Randall Munroe&lt;/strong> &amp;mdash; If you like xkcd you&amp;rsquo;re going to like this.&lt;/li>
&lt;li>&lt;strong>Numbers Don&amp;rsquo;t Lie: 71 Things You Need to Know About the World by Vaclav Smil&lt;/strong> &amp;mdash; This was a recommendation from my brother. I found it valuable to read something so quantitative in a world where we consume so much qualitative content.&lt;/li>
&lt;li>&lt;strong>Man&amp;rsquo;s Search for Meaning by Viktor Frankl&lt;/strong> &amp;mdash; Definitely a tough read but worth it to be exposed to the dynamic range of humanity.&lt;/li>
&lt;/ol>
&lt;h2 id="top-10-others">Top 10 Others&lt;/h2>
&lt;p>This is to capture the music, podcasts, Youtubers, video games, and board games that I really enjoyed this year.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Magic: The Gathering&lt;/strong> &amp;mdash; Magic is a &lt;a href="https://en.wikipedia.org/wiki/Collectible_card_game">collectible card game&lt;/a> that I was first introduced to in junior high by my friend. I have really fond memories of hours spent in his basement learning to play. For a number of reasons we eventually stopped playing. However, this summer when I was in Seattle (the birthplace and mecca of Magic) I had a hankering to start playing again. There are multiple formats of Magic that you can play but by far my favorite is limited. In limited you sit around a table with 8 others passing packs of cards around as you draft a deck that you&amp;rsquo;ll then play against your opponents. Since this summer I have made a very serious return to Magic and I&amp;rsquo;m absolutely loving it. I firmly believe that limited Magic is one of the funnest and most mentally stimulating ways to spend a few hours.&lt;/li>
&lt;li>&lt;strong>Harry&amp;rsquo;s House by Harry Styles&lt;/strong> &amp;mdash; I basically just listened to this album on repeat all year. According to Spotify I was in the top 0.5% of listeners. My favorite songs were Daylight and Love Of My Life. I&amp;rsquo;ll forever associate his second album with my trip to New Zealand and this album will forever be associated with my British Isles trip.&lt;/li>
&lt;li>&lt;strong>All-In podcast&lt;/strong> &amp;mdash; The All-In podcast continues to deliver. Although the show can sometimes get a little more political than I prefer, I am still addicted to feeling like I have secret access to the conversations of billionaires.&lt;/li>
&lt;li>&lt;strong>Limited Resources podcast&lt;/strong> &amp;mdash; This podcast is all about how to get better at playing Magic. I crushed so many of these episodes on my commute to work in the summer. Oddly enough I feel like I learned a lot of general life lessons from this podcast too.&lt;/li>
&lt;li>&lt;strong>Acquired podcast&lt;/strong> &amp;mdash; Never did I think I would be listening to 9 hour episodes about people like Warren Buffet, but Acquired is so worth listening to. They also put on a killer in person show in Seattle that I was lucky enough to attend.&lt;/li>
&lt;li>&lt;strong>Mr. Morale and the Big Steppers by Kendrick Lamar&lt;/strong> &amp;mdash; I have a fairly limited exposure to rap, but I really like Kendrick Lamar. His latest album did not disappoint.&lt;/li>
&lt;li>&lt;strong>Lords of Limited podcast&lt;/strong> &amp;ndash; This podcast is also all about how to get better at playing Magic. Are you noticing a theme of how into Magic I am?&lt;/li>
&lt;li>&lt;strong>Who Cares? by Rex Orange County&lt;/strong> &amp;mdash; I really enjoyed this album. I first listened to it when driving up to Grand Prairie and I find I always love albums that are associated in my mind with travel. It also was really special to be able to watch Rex perform it live.&lt;/li>
&lt;li>&lt;strong>Adam Ragusea&lt;/strong> &amp;mdash; There is a lot of different Youtube creators that I watch but Adam Ragusea definitely rises to the top. I&amp;rsquo;m particularly fond of how he approaches learning new things.&lt;/li>
&lt;li>&lt;strong>It Takes Two&lt;/strong> &amp;mdash; This is a co-op video game that I played with my friend. I&amp;rsquo;m not going to spoil anything but the premise kept getting crazier than I expected it to.&lt;/li>
&lt;/ol>
&lt;h2 id="reflecting-on-2022">Reflecting on 2022&lt;/h2>
&lt;h3 id="career">Career&lt;/h3>
&lt;blockquote>
&lt;p>By the end of this year I&amp;rsquo;ll graduate. With that in mind I&amp;rsquo;ll need to start making some decisions about where I want to work full-time and where I want to live. I also want to consider less traditional options like digital nomading or consulting for the start of 2023.&lt;/p>&lt;/blockquote>
&lt;p>Lots of wins for my career in 2022. I completed my Computer Engineering degree and I accepted an offer at &lt;a href="https://fermyon.com">Fermyon&lt;/a> where I&amp;rsquo;ll start working in January. Although I didn&amp;rsquo;t really explore consulting as an option, digital nomading is definitely front of mind for me. The two forms of this I&amp;rsquo;m most interested in our tacking travel on to company on-sites and joining a &lt;a href="https://www.remoteyear.com/">digital nomad group&lt;/a>.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Graduate!&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Leverage my connections to have multiple full-time offers post-graduation.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Seriously research the possibility of digital nomading or consulting at the start of 2023.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure">Travel and Adventure&lt;/h3>
&lt;blockquote>
&lt;p>Thanks to remote schooling and work I spend a lot of time at home. Traveling abroad and doing adventurous things is a great way to spice up my life.&lt;/p>&lt;/blockquote>
&lt;p>I&amp;rsquo;m counting my British Isles trip as equivalent to a trip to Eastern Canada. I&amp;rsquo;ll be less specific in the future about locations because travel plans are always fluid. Unfortunately, I didn&amp;rsquo;t end up canoeing much this year. I was also only able to bike a metric century rather than a full imperial century. Despite missing these goals I still feel like I had a very adventurous year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Travel to Eastern Canada.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do a multi-night canoe trip on the North Saskatchewan.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Bike an imperial century.&lt;/li>
&lt;/ul>
&lt;h3 id="health">Health&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;ve had some trouble with physical and mental health problems over 2021. Looking forward to 2022 I want to take a more holistic approach to my health, or in other words, I want to do a lot of yoga.&lt;/p>&lt;/blockquote>
&lt;p>On the whole I think I successfully managed my health throughout 2022 despite encountering a lot of injuries and setbacks. I&amp;rsquo;ve been very intentional about rehabilitating the injuries and proactively preventing new ones. While I don&amp;rsquo;t do yoga weekly anymore it is now something I am comfortable doing and know will benefit me. I was also able to train for biking a metric century despite a very busy fall. Perhaps best of all I got into hockey in a big way this year and joined a beer league team that I&amp;rsquo;m really excited to play on.&lt;/p>
&lt;p>Starting in May I&amp;rsquo;ve been doing the &lt;a href="https://stronglifts.com/5x5/">Stronglifts 5x5&lt;/a> program. Since then I&amp;rsquo;ve had to stop twice due to traveling and injury but otherwise I&amp;rsquo;ve been very consistent. I&amp;rsquo;ve really been enjoying this process. As of publishing this I am: squatting 190 lbs, deadlifting 195 lbs, bench pressing 110 lbs, barbell rowing 100 lbs, and overhead pressing 70 lbs. This wasn&amp;rsquo;t quite as much progress from my midpoint check-in as I was hoping but I&amp;rsquo;m mostly pleased that I&amp;rsquo;m still working at it. More impressive numbers will come with time.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Build a habit of doing yoga multiple times a week.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Do a mix of body weight exercises at home and weightlifting at the gym.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep biking and training for a century.&lt;/li>
&lt;/ul>
&lt;h3 id="digital-life">Digital Life&lt;/h3>
&lt;blockquote>
&lt;p>I find the digital world to be both a blessing and a curse, software itself is amoral but amplifies the underlying good or bad. On one hand I&amp;rsquo;ve never been more connected, empowered, and informed. On the other hand I&amp;rsquo;ve never felt so isolated, paralyzed, and confused. Not quite sure how to get off the treadmill or if I even want to, but the following seem like some rational first steps.&lt;/p>&lt;/blockquote>
&lt;p>No progress here. It&amp;rsquo;s becoming more clear to me that the only way I&amp;rsquo;ll see any success here is by going cold turkey which I don&amp;rsquo;t feel prepared to do.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Avoid having my phone be the first thing I interact with in the morning.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Limit the empty calories I consume i.e. Instagram, Youtube, etc.&lt;/li>
&lt;/ul>
&lt;h3 id="finances">Finances&lt;/h3>
&lt;blockquote>
&lt;p>2021 was a big year for my finances: I read some great investing books; started investing in crypto; got my traditional investments in order; and built a big spreadsheet to help me keep track of my overall financial direction. I&amp;rsquo;d be happy if all I did in 2022 was continue to dollar cost average into my investments and keep my spreadsheet up to date — compounding will take care of the rest.&lt;/p>&lt;/blockquote>
&lt;p>I&amp;rsquo;m really happy with my progress here. Overall I managed to graduate with limited debt and some meaningful assets all while still being able to travel and not feeling like I was living paycheck to paycheck. I was also able to secure a well paying job for the first job of my career.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep dollar cost averaging into passive index funds, ETH, BTC.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep my spreadsheet up to date.&lt;/li>
&lt;/ul>
&lt;h3 id="building">Building&lt;/h3>
&lt;blockquote>
&lt;p>Building things makes me happy. I don&amp;rsquo;t intend to be prescriptive about what I&amp;rsquo;ll build in 2022 — all that matters is that I build something.&lt;/p>&lt;/blockquote>
&lt;p>The coolest and most complete thing I built this year was &lt;a href="https://calebschoepp.com/blog/2022/building-studybuddy-at-hacked-2022/">StudyBuddy&lt;/a>. I didn&amp;rsquo;t really finish anything else but I did hack on a lot of different things: Postgres based product analytics, the inception studio project, a web assembly interpreter, a compiler, and a few more things I&amp;rsquo;m probably forgetting.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Build and publish 2+ projects.&lt;/li>
&lt;/ul>
&lt;h3 id="reading">Reading&lt;/h3>
&lt;blockquote>
&lt;p>Last year I focused on reading less short/medium-form blog content and more long-form book content. Blogs have their place, but I felt that I was missing some of the nuance you find in books. I also wanted to rediscover fiction. It was a good start last year but I want to pick up the pace. The Kobo I got for christmas should hopefully assist with this.&lt;/p>&lt;/blockquote>
&lt;p>Give or take I read about 13 books in 2022. While I didn&amp;rsquo;t quite meet the goal I developed a good habit of reading a chapter or two before bed each night. One chapter isn&amp;rsquo;t very much but it adds up when I&amp;rsquo;m consistent. The Kobo was also very helpful in getting me to read more.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 1.5+ books a month (for a total of 18+ over the year).&lt;/li>
&lt;/ul>
&lt;h3 id="writing">Writing&lt;/h3>
&lt;blockquote>
&lt;p>I tend to overthink (understatement of the year) so I should definitely be writing more. In the first year of this blog I managed to write eight posts. I chalk it up to a productive spurt in the early COVID lockdowns. In 2021 I only managed to write five. I think it&amp;rsquo;d be a big win if I wrote ten or more posts in 2022. Writing has compounding benefits so I want to start the compounding sooner than later.&lt;/p>&lt;/blockquote>
&lt;p>Including this post I was able to publish 14 blog posts in 2022 well exceeding my goal. One of my posts went viral on Hacker News and accrued more than 60k visitors to my website growing my email list to a little more than 100 people. In the grand scheme of things these are still very small numbers. However, back in 2020 when I started blogging I would have been flabbergasted to see these numbers. I&amp;rsquo;m sure that if I keep up this pace, these numbers will continue to grow at a compounding rate.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Publish 10+ blog posts.&lt;/li>
&lt;/ul>
&lt;h3 id="guitar">Guitar&lt;/h3>
&lt;blockquote>
&lt;p>Learning guitar was one of the best decisions I made in 2021 so I want to keep my foot on the gas pedal. The hardest thing here will be continuing to do focused practice; at the one year mark I have enough skill that I find it really easy to waste a whole practice session just noodling rather than working on the fundamentals.&lt;/p>&lt;/blockquote>
&lt;p>Identifying the risk of noodling rather than practicing was very prescient of me. This has been a big issue for me in 2022 and is the main reason why I made so little progress on grade 3, ear training, and theory. I&amp;rsquo;m not too mad about it because I have fun noodling and music is ultimately about fun for me &amp;mdash; but it would still be nice to grow as a musician.&lt;/p>
&lt;p>I did however have a lot of success in playing in front of people and jamming with other players. I spent a lot of time going out of my comfort zone doing these things this year.&lt;/p>
&lt;p>In hindsight the stretch goal was maybe too ambitious for me this year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Beginner Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Ear Training and Theory up to Grade 3.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep playing in front of family and friends to practice performing.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Find more people to jam with.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Record a cover of a song.&lt;/li>
&lt;/ul>
&lt;h3 id="web-30">Web 3.0&lt;/h3>
&lt;blockquote>
&lt;p>2021 was the year that I got red-pilled by crypto; I started buying ETH and BTC; I started mining HNT; but most of all I read and listened to a lot of crypto content. In 2022 I want to double down on actually tinkering around with crypto. I&amp;rsquo;m convinced that Web 3.0 is going to be huge so I want to get in early. The end goal of the tinkering is to discover what still needs to be built and to go build it.&lt;/p>&lt;/blockquote>
&lt;p>My thoughts here haven&amp;rsquo;t changed since the halfway check-in. Besides continuing to dollar cost average into ETH and BTC I have made no progress here. Frankly I think that my passion here has just cooled off which I am more than okay with. I’m still convinced (even in the current bear market) that there will be a myriad of world changing use cases for crypto. I’m also still convinced that there is an adequate possibility of a highly asymmetric upside for owning crypto on a multi-decade time horizon. All that being said I currently have no desires to build or actively participate in the space. Besides investing I’ll still keep hosting Helium miners cause it is low effort and fun. I’d also still like to pick up some ENS domains.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Continue to dollar cost average my way into ETH and BTC.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Purchase or host more Helium miners.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Register an ENS domain.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Try out some DeFi protocols.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Buy some NFTs and get involved with the communities.&lt;/li>
&lt;/ul>
&lt;h2 id="looking-forward-to-2023">Looking Forward to 2023&lt;/h2>
&lt;h3 id="career-1">Career&lt;/h3>
&lt;p>After nearly six years of university and internships I feel that I&amp;rsquo;ve learned how to excel in these domains. I&amp;rsquo;m now entering into a whole new world of full-time work. I&amp;rsquo;m going to need to learn a lot of new skills and lessons to start mastering this domain.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Find and maintain a sustainable work-life balance.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Actively seek 1-1&amp;rsquo;s to learn and build relationships with teammates.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Write a blog post in January detailing my plan and goals for my new job more thoroughly.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Start building a professional network in Edmonton.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Go to 2+ technical conferences.&lt;/li>
&lt;/ul>
&lt;h3 id="finances-1">Finances&lt;/h3>
&lt;p>Thanks to hard work, networking, and some luck I&amp;rsquo;ve found myself in a position where I&amp;rsquo;m going to be making more money than I&amp;rsquo;m used to. Given this opportunity I want to take maximum advantage of it and save as much as I can. Savings now are going to compound a lot more than savings I accrue when I&amp;rsquo;m older. Not to mention the fact that I&amp;rsquo;m single and have no dependents which provides me with lots of flexibility in how I spend my money.&lt;/p>
&lt;p>So given this sudden increase in income and desire to save a large percentage of it, I have decided I need to strap some training wheels on for the year. In other words I&amp;rsquo;ve decided that I need to start monthly budgeting again. My hope is the monthly budget will allow me to save significantly while also spending my money on fun things guilt free.&lt;/p>
&lt;p>How I put this money I save to work is a more complicated question that I plan to answer throughout the year. There are many things I can do with it: pay off my debt; build a sizeable emergency fund; save for travel in the mid-term; invest for retirement in the long-term. The ordering and weighting of these uses is to be determined.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Use a monthly budget like &lt;a href="https://ynab.com">YNAB&lt;/a> all year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep my high level spreadsheet updated.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Donate 10% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Save a minimum of 15% of my gross income.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Research investment strategies outside of RRSP.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Save 30% of my gross income.&lt;/li>
&lt;/ul>
&lt;h3 id="physical-health">Physical Health&lt;/h3>
&lt;p>This year I&amp;rsquo;ve split physical and mental health into separate categories. While they are tightly linked in how the affect each other, I think it is helpful to view my goals for them as separate.&lt;/p>
&lt;p>The main thrust of my physical health goals is to continue with Stronglifts 5x5. I&amp;rsquo;ve set my year end goal based on the &lt;a href="https://stronglifts.com/5x5/#Strength_Goals">Intermediate I&lt;/a> goals found on the website translated to 5 rep maxes. The secondary thrust is to continue to build a resilient body through things like yoga and stretching so that I get injured less.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Do yoga every day in January.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Play a sport (hockey) once a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Workout (yoga/weightlifting/etc.) three times a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Actually warmup, cooldown, and stretch when I workout.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 5RM of: squat 255 lbs, deadlift 355 lbs, bench press 175 lbs, barbell row 155 lbs, and overhead press 90 lbs.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Workout (yoga/weightlifting/etc.) five times a week.&lt;/li>
&lt;/ul>
&lt;h3 id="mental-health">Mental Health&lt;/h3>
&lt;p>My mental health has a huge impact on my day to day life so I want to continue to actively work to improve it. In a previous &lt;a href="/blog/2022/inception-studio-weekend-reflections/#the-value-of-regular-therapy-or-life-coaching">post&lt;/a> I talked about how I think more regular therapy could be valuable. I want to try and enact that this year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build life coaching or therapy into my life on a regular cadence.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep using yoga as a tool to aid in my mental health.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Avoid having my phone be the first thing I interact with in the morning.&lt;/li>
&lt;/ul>
&lt;h3 id="relationships">Relationships&lt;/h3>
&lt;p>This is a new category I&amp;rsquo;m adding this year. It has become abundantly clear to me over the past year how good relationships are one of the most important factors to my happiness. Given that I&amp;rsquo;ve graduated university and decided to work for a remote company I am going to need to be extremely proactive about finding, building, and maintaining relationships.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Plan semi-regular lunches with friends and family in town as a way to get out of the house.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Get involved in 1-2 activities where I can meet people e.g. hockey team, dance lessons, etc.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Host friends and family more often at my place.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Be proactive about setting up calls with friends who live far away.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure-1">Travel and Adventure&lt;/h3>
&lt;p>Truth be told I&amp;rsquo;m not sure the best way to set goals in this area of my life. If I&amp;rsquo;m too specific than I always miss the goals because plans change. But, if I&amp;rsquo;m too generic I don&amp;rsquo; think the goals are in any way valuable. For this year what I&amp;rsquo;ll try doing is listing some adventures that I&amp;rsquo;m entirely confident I want to do. I&amp;rsquo;ll also include some stretch goals of things that sound fun and that I want to look into, but that I&amp;rsquo;m not ready to fully commit to.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Visit friends in Seattle.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Bike an imperial century.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Do a multi-night canoe trip on the North Saskatchewan.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Join a digital nomad group like Remote Year.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Plan a solo-trip to another country I haven&amp;rsquo;t been to.&lt;/li>
&lt;/ul>
&lt;h3 id="building-1">Building&lt;/h3>
&lt;p>This is one of the few categories where I&amp;rsquo;m setting a less ambitious goal. I&amp;rsquo;m very excited about building cool things and I think that long term it has compounding effects. However, now that I&amp;rsquo;m graduating I feel that I&amp;rsquo;ve entered a time in my life where I&amp;rsquo;m more focused on finding balance in my life as opposed to obsession. Put another way, I feel less of desire to grind on side projects than I did a year ago. I&amp;rsquo;m sure this desire will ebb and flow with time.&lt;/p>
&lt;p>Although I&amp;rsquo;m only aiming for one project I&amp;rsquo;m sure it will take me a couple tries to find an idea that I&amp;rsquo;m really excited to hack on. I also want to highlight the importance of the publishing and writing about it steps. I&amp;rsquo;m not going to consider a project done unless I do those too.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build, publish, and write about, one meaningful project.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Organize a low-key weekend hackathon with my friends.&lt;/li>
&lt;/ul>
&lt;h3 id="reading-1">Reading&lt;/h3>
&lt;p>Nothing crazy here. I want to continue to build the habit of reading books because I think it is a good counter-measure against the rapid pace of our digital world. In terms of genres I&amp;rsquo;m open to reading any mix of fiction, non-fiction, and self-help.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 1.5+ books a month (for a total of 18+ over the year).&lt;/li>
&lt;/ul>
&lt;h3 id="writing-1">Writing&lt;/h3>
&lt;p>I&amp;rsquo;m really pleased with how my blogging went last year and all I&amp;rsquo;m aiming to do in 2023 is keep a similar cadence. I could set a really aggressive stretch goal here but I don&amp;rsquo;t think I actually desire to write that much. My perspective is that about one post a month will still give me the benefits I&amp;rsquo;m looking for. I&amp;rsquo;m also not going to set any goals about having posts go viral &amp;mdash; that either happens or it doesn&amp;rsquo;t.&lt;/p>
&lt;p>The only thing I would change about my writing last year was that I wish it was more consistent. I think it is better to write one post a month rather than four posts one month and then zero the next. Consistency will not only help me build discipline in the practice of writing but it will also build my credibility and audience.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 12+ blog posts.&lt;/li>
&lt;/ul>
&lt;h3 id="music">Music&lt;/h3>
&lt;p>I&amp;rsquo;m so pleased that two years ago I decided to pick up guitar. Doing so filled a hole in my life that I wasn&amp;rsquo;t aware of &amp;mdash; making music. As a kid music was a big part of my life but it faded in university. Suffice to say I&amp;rsquo;m glad it&amp;rsquo;s back. Music is a more apt name for this category because my interests are starting to wander beyond guitar. In addition to my goals of progressing on guitar I&amp;rsquo;m also starting to have the desire to pick up piano too. I&amp;rsquo;m leaving it as a stretch goal and we&amp;rsquo;ll see where the year takes me.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Grade 3 and finish 3 modules in Justin Guitar Grade 4.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Ear Training and Theory up to Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Learn to use a looper with &lt;a href="https://learnpracticeplay.com/loop-school-signup/">this&lt;/a> course.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep playing in front of family and friends to practice performing.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep jamming with other guitarists.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Record a cover of a song.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Start taking piano lessons.&lt;/li>
&lt;/ul>
&lt;h2 id="closing-thoughts">Closing Thoughts&lt;/h2>
&lt;p>I&amp;rsquo;m really excited for what 2023 has to bring. I&amp;rsquo;m going through a lot of change in my life so I&amp;rsquo;m sure there will be plenty of highs and plenty of lows. Now if you&amp;rsquo;ll excuse me, I need to go ring in the new year. Talk soon.&lt;/p></description></item><item><title>Inception Studio Weekend Reflections</title><link>/blog/2022/inception-studio-weekend-reflections/</link><pubDate>Tue, 06 Dec 2022 00:00:00 +0000</pubDate><guid>/blog/2022/inception-studio-weekend-reflections/</guid><description>&lt;p>I recently spent a weekend in California at a hackathon put on by Inception Studio. For three days I hacked on large language model (LLM) ideas with some really smart people. I learned so much from the experience that I wanted to write down some of my biggest learnings.&lt;/p>
&lt;h2 id="life">Life&lt;/h2>
&lt;h3 id="take-more-risk">Take more risk&lt;/h3>
&lt;p>At this time in my life I should take as much risk as I can fathom. Taking large risks early in life means you have a longer time to compound on the successful outcomes of these risks. I also have multiple factors that make me very resilient to failure:&lt;/p>
&lt;ul>
&lt;li>No dependents to support.&lt;/li>
&lt;li>A stable family to catch me if I fail.&lt;/li>
&lt;li>Pedigree from a Computer Engineering degree at a top Canadian university.&lt;/li>
&lt;li>Work experience and connections making it highly likely I could find work.&lt;/li>
&lt;/ul>
&lt;h3 id="the-value-of-regular-therapy-or-life-coaching">The value of regular therapy or life coaching&lt;/h3>
&lt;p>I&amp;rsquo;ve seen therapists in acute contexts and it has been transformational for me. Correspondingly I&amp;rsquo;ve become a big advocate for therapy. However, I&amp;rsquo;ve always tied it to acute issues in my life. What if I integrated it more regularly into my life?&lt;/p>
&lt;p>Programmers spend most of the day inside their own head. So it is critical that they keep their head on straight. Speaking from personal experience I can say that dips in my mental health can wreak havoc on my productivity (not mentioning its effects on the rest of my life). It only makes sense that mental health should be regularly cared for like physical health. I want to explore more regular therapy or life coaching.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;h3 id="the-importance-of-low-ego">The importance of low ego&lt;/h3>
&lt;p>A big ego is like a bubble around you that keeps people from getting close. Keep your ego low and you&amp;rsquo;ll find building relationships, seeking advice, and asking favors all easier.&lt;/p>
&lt;h2 id="career">Career&lt;/h2>
&lt;h3 id="prioritize-working-with-smart-people">Prioritize working with smart people&lt;/h3>
&lt;p>Perhaps my favorite part of the weekend was being surrounded by so many smart and motivated people. Rubbing shoulders with people smarter than me drastically accelerated my learning. I want to optimize for having more opportunities to surround myself with people like this.&lt;/p>
&lt;p>Hackathons are the most obvious way to inject more of this into my life. As I exit my undergrad I&amp;rsquo;ll have to be more intentional about seeking these opportunities. A variation on this would be to throw mini-hackathons with friends I know. Another thing I want to explore are &lt;a href="https://www.remoteyear.com/">digital nomad groups&lt;/a>. Combining traveling the world with meeting cool people sounds great.&lt;/p>
&lt;h3 id="the-moral-imperative-to-have-the-difficult-conversation">The moral imperative to have the difficult conversation&lt;/h3>
&lt;p>When I asked one of the attendees for advice on being an engineer manager I was given some advice that I thought was very profound.&lt;/p>
&lt;p>Imagine that one of your direct reports is doing something problematic. In your gut you know you should have a conversation with them and bring up the issue. However, this requires some confrontation and isn&amp;rsquo;t easy to do. So you take the easy way out and don&amp;rsquo;t say anything. Naturally the problem persists and gets worse over time. As months pass, the problem grows into something that can no longer be ignored. You need to fire this employee because of the problem. When you go to fire them they of course ask you why. You explain that, &amp;ldquo;it is because of the problem&amp;rdquo;, to which they say, &amp;ldquo;why didn&amp;rsquo;t you tell me about the problem?!&amp;rdquo; No one is happy.&lt;/p>
&lt;p>The point of the story is that you have a moral imperative as a manager to have these difficult conversations early. Not doing so is just setting up everyone for more pain and difficulty.&lt;/p>
&lt;h3 id="a-mental-model-for-entrepreneurship">A mental model for entrepreneurship&lt;/h3>
&lt;p>Only two things matter in entrepreneurship: product market fit and founder product fit.&lt;/p>
&lt;p>Product market fit means that you are providing value to the world by filling a real market demand. To find it you need to iterate on the cycle of building things and talking to customers. Both elements are critical. But, it doesn&amp;rsquo;t matter where you start so long as you keep iterating as quickly as you can.&lt;/p>
&lt;p>Founder product fit means that you love the problem space you&amp;rsquo;re working in. This ensures that you&amp;rsquo;ll have the intensity and longevity necessary to find product market fit. To find founder product fit you need to try and build a lot of things and see what resonates with you.&lt;/p>
&lt;p>Put more simply the most important element of entrepreneurship is taking action.&lt;/p>
&lt;h3 id="knowing-when-you-have-product-market-fit">Knowing when you have product market fit&lt;/h3>
&lt;p>If it isn&amp;rsquo;t completely obvious to you that you have it &amp;ndash; then you don&amp;rsquo;t have it. It should feel like you&amp;rsquo;re being pulled out to ocean by a riptide.&lt;/p>
&lt;p>I&amp;rsquo;ve never experienced true product market fit with anything I&amp;rsquo;ve built. The closest experience I&amp;rsquo;ve had is when one of my &lt;a href="/blog/2022/productivity-porn/">posts&lt;/a> went to #2 on Hacker News. In the span of a week more than 60k people read my post, news aggregators around the internet were picking it up, and I received numerous personal emails from people around the world. Previous blog posts felt like I was yelling in to the void. This one felt like I had reporters banging on my door pulling the story out of me.&lt;/p>
&lt;p>The ultimate goal when I&amp;rsquo;m building a product is to achieve product market fit. Remembering this feeling of being pulled vs. pushed will help me know when I&amp;rsquo;ve found product market fit and when I should keep looking.&lt;/p>
&lt;h2 id="llms">LLM&amp;rsquo;s&lt;/h2>
&lt;h3 id="new-mental-models-for-llms">New mental models for LLM&amp;rsquo;s&lt;/h3>
&lt;p>Each of these mental models are too simple too accurately describe an LLM. But, this is exactly what makes them useful shorthands for understanding LLM&amp;rsquo;s.&lt;/p>
&lt;p>An LLM is a compression function run across the entire internet. When you ask GPT-3 a question you&amp;rsquo;re really just asking that question to the mean of the internet.&lt;/p>
&lt;p>An LLM is like an axe. The size of the axe head is the size of the model; GPT-2 is a hatchet; GPT-3 is a logging axe. Fine tuning is like sharpening your axe. How you prompt the LLM is like how you wield your axe. As you try to chop through tougher wood (thornier problems) you either need a bigger axe head, a sharper blade, or better technique &amp;ndash; or all of the above.&lt;/p>
&lt;p>An LLM is like an alien species who has landed on earth. They are vastly more intelligent than us. For some reason we all get to take turns asking the alien questions. But, they don&amp;rsquo;t always understand are questions in the way we do. If we spend time learning how to ask them questions in just the right way we can unlock their vast intelligence.&lt;/p>
&lt;h3 id="the-llm-space-needs-more-tooling">The LLM space needs more tooling&lt;/h3>
&lt;p>I think that the LLM space is in massive need of more/better tooling. Prompt engineering feels distinct from software engineering and machine learning engineering. As such it needs its own tools for testing, debugging, editing, and deploying LLM&amp;rsquo;s. One example of a gap is the core loop of prompt engineering: modifying a prompt to see if you can produce better results. It felt like I was flying completely blind here. I would make a change to my prompt and have no way to tell if the change I made was actually an improvement beyond manually looking at an output or two.&lt;/p>
&lt;h3 id="the-future-of-llms-is-very-exciting">The future of LLM&amp;rsquo;s is very exciting&lt;/h3>
&lt;p>This weekend has gotten me very excited for the future of LLM&amp;rsquo;s. I didn&amp;rsquo;t pay much attention when GPT-3 came out but you can be sure that I&amp;rsquo;ll be on the edge of my seat for the release of GPT-4. I think that it is going to display some emergent properties that are hard to fathom right now. We are somewhere near the start of an s-curve of innovation &amp;ndash; a very exciting place to be. I have lots of questions about the future of LLM&amp;rsquo;s that I look forward to getting answered:&lt;/p>
&lt;ul>
&lt;li>Will future models like GPT-4 become prohibitively expensive as they get increasingly larger?&lt;/li>
&lt;li>Will LLM&amp;rsquo;s turn into commodities like cloud computing did?&lt;/li>
&lt;li>How truly capable are LLM&amp;rsquo;s? To what extent are we hitting fundamental limitations vs. just failing to properly prompt them?&lt;/li>
&lt;li>How far will the transform architecture take us? Is attention truly all we need? 😉&lt;/li>
&lt;/ul>
&lt;h3 id="respect-the-llms">Respect the LLMs&lt;/h3>
&lt;p>Andy Chou kept reminding me that we needed to, &amp;ldquo;respect the LLM&amp;rsquo;s more.&amp;rdquo; Kind of a silly thing to say but the message really resonated with me. LLM&amp;rsquo;s are more capable than we think and when I &amp;ldquo;respected&amp;rdquo; the model (a.k.a gave the model a prompt I didn&amp;rsquo;t think it would be able to handle) I was amazed at the results it was able to achieve.&lt;/p>
&lt;hr>
&lt;p>&lt;em>Special thanks to Andy, Ian, Greg, Brian, and Oliver for some of these insights.&lt;/em>&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>I was also exposed to a really interesting &lt;a href="http://neuroticgradientdescent.blogspot.com/2021/03/threefold-training.html">blog&lt;/a> about mental health that I intend to read a lot more.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>A Programmer's Three Identities</title><link>/blog/2022/a-programmers-three-identities/</link><pubDate>Thu, 25 Aug 2022 00:00:00 +0000</pubDate><guid>/blog/2022/a-programmers-three-identities/</guid><description>&lt;p>One of the reasons I enjoy programming so much is that it asks you to to simultaneously take on three unique identities. Each identity requires you to think differently and the identities may be called upon to different levels depending on the programming task. And it is absolutely possible to program with only one or two of these identities. But, to be a truly great programmer you must be able to assume all three identities.&lt;/p>
&lt;h2 id="the-scientist">The Scientist&lt;/h2>
&lt;p>The first identity is the scientist. This is typically the first identity acquired by students in the traditional university pipeline. In academia the scientist&amp;rsquo;s role is essential; in industry the scientist takes a backseat while occasionally being critically important.&lt;/p>
&lt;p>Unsurprisingly, the scientist is concerned with the field of computer science: algorithms, complexity theory, parsing techniques, statistical methods and other things of this ilk. The most important aspect of a program to the scientist is whether it provably achieves something. How the code looks or is deployed are irrelevant details. Above all the scientist focuses on the possibilities of computing.&lt;/p>
&lt;h2 id="the-craftsmen">The Craftsmen&lt;/h2>
&lt;p>The second identity is the craftsmen&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Commonly this identity is discovered after one has spent sufficient time honing their identity as a scientist. Those who were introduced to programming at a young age often pick this up as their first identity.&lt;/p>
&lt;p>The craftsmen recognizes that programming is not just a science but also an art. They take immense pride in the clarity, consistency, and conciseness of their code. A great craftsmen is concerned with the smallest minutia like ensuring their is always a space after a comma in a list of items. Refactoring is a delightful opportunity for a craftsmen &amp;ndash; not a dreaded chore. It is a pleasure to live in the codebase of a craftsmen. Above all the craftsmen focuses on the joy of programming.&lt;/p>
&lt;h2 id="the-engineer">The Engineer&lt;/h2>
&lt;p>The third and final identity is the engineer. This identity is developed when you first come in contact with code operating in production. Production is where the rubber hits the road as your program is used to solve real-world problems.&lt;/p>
&lt;p>The engineer&amp;rsquo;s primary concern is how a program can be delivered and managed to reliably solve these real-world problems. Tools are an engineers best friend: monitoring/alerting systems, CI/CD, performance instrumentation, distributed tracing, etc. When tasks are repetitive and important the engineer automates them. An engineer isn&amp;rsquo;t afraid to get dirty solving a weird bug in production and they aren&amp;rsquo;t afraid to jerry-rig a not-so-pretty but effective solution. Above all the engineer focuses on the practical issues that arise when a program enters the real-world.&lt;/p>
&lt;h2 id="the-programmer">The Programmer&lt;/h2>
&lt;p>The programmer contains the multitudes of all these identities. They know when it is time to develop the perfectly optimal solution and when the &lt;code>O(n)&lt;/code> solution is good enough; when it is time to fix that one obscure linting rule and when it is time to just hit save and move on cause it works; when it is time to build a well tuned deployment pipeline and when a Jupyter notebook will do just fine.&lt;/p>
&lt;p>This is an incredibly difficult balancing act and one of the reasons why I love programming. I&amp;rsquo;m just at the beginning of my journey with each of these identities and I can&amp;rsquo;t wait to go deeper into each one of them.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Or craftswoman. We&amp;rsquo;ll use craftsmen since that is what I identify with.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>2022 Halfway Check-In</title><link>/blog/2022/2022-halfway-check-in/</link><pubDate>Wed, 03 Aug 2022 00:00:00 +0000</pubDate><guid>/blog/2022/2022-halfway-check-in/</guid><description>&lt;p>My &lt;a href="/blog/2021/year-in-review-2021/">2021 year in review&lt;/a> outlines my goals for 2022. Now that we are a bit more than halfway through the year I thought it would be a good idea to stop and reflect on these goals&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Time to see where I&amp;rsquo;ve excelled and where I&amp;rsquo;ve slacked and determine what is still worthy of pursuing and what I should &lt;a href="/blog/2022/when-should-i-give-up/">give up on&lt;/a>.&lt;/p>
&lt;h2 id="checking-in-on-2022">Checking In On 2022&lt;/h2>
&lt;h3 id="career">Career&lt;/h3>
&lt;blockquote>
&lt;p>By the end of this year I&amp;rsquo;ll graduate. With that in mind I&amp;rsquo;ll need to start making some decisions about where I want to work full-time and where I want to live. I also want to consider less traditional options like digital nomading or consulting for the start of 2023.&lt;/p>&lt;/blockquote>
&lt;p>This is an interesting time to be reflecting on this aspect of my life as I am very much in the thick of it right now. After lots of reflection I&amp;rsquo;ve decided that I want to work a remote job where I have the geographic flexibility to both live by my family and travel the world. It is to be determined which specific company I will end up at.&lt;/p>
&lt;p>While there is basically no forseeable way for me to not graduate at this point, I&amp;rsquo;ll save the gratification of checking it off until the moment comes.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Graduate!&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Leverage my connections to have multiple full-time offers post-graduation.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Seriously research the possibility of digital nomading or consulting at the start of 2023.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure">Travel and Adventure&lt;/h3>
&lt;blockquote>
&lt;p>Thanks to remote schooling and work I spend a lot of time at home. Travelling abroad and doing adventurous things is a great way to spice up my life.&lt;/p>&lt;/blockquote>
&lt;p>For future years I should be less specific about where I want to travel unless plans have materialized or I&amp;rsquo;m hellbent on going there. Rather than Eastern Canada I&amp;rsquo;ll be travelling to Scotland and Ireland at the end of the summer&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>. I&amp;rsquo;m still planning to canoe a lot and bike an imperial century. Nothing completed here yet but definitely on track in this area of my life. I even managed to get a bonus trip to Portland in too.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Travel to Eastern Canada.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do a multi-night canoe trip on the North Saskatchewan.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Bike an imperial century.&lt;/li>
&lt;/ul>
&lt;h3 id="health">Health&lt;/h3>
&lt;blockquote>
&lt;p>I&amp;rsquo;ve had some trouble with physical and mental health problems over 2021. Looking forward to 2022 I want to take a more holistic approach to my health, or in other words, I want to do a lot of yoga.&lt;/p>&lt;/blockquote>
&lt;p>2022 has largely been a good year for my health &amp;ndash; both mental and physical. At the start of the year I was doing daily yoga and reaped commensurate benefits. Although I&amp;rsquo;ve since stopped doing daily yoga I&amp;rsquo;m going to count this as a success. I&amp;rsquo;ve developed a love for yoga and have it my back pocket now for days when I&amp;rsquo;m feeling physically stiff or mentally frayed.&lt;/p>
&lt;p>In late May I began consistently weightlifting with the &lt;a href="https://stronglifts.com/5x5/">Stronglifts 5x5&lt;/a> program and have been &lt;strong>really&lt;/strong> enjoying it. I&amp;rsquo;ve had multiple failed attempts at starting to lift in the past but it really seems to be sticking this time. I&amp;rsquo;m two months in and I still get excited to go to the gym every morning. Spending some time with a trainer at the start was really helpful for solidifying proper form for the lifts (which was a big challenge for me in the past). As of July 21 I am: squatting 170 lbs, deadlifting 185 lbs, bench pressing 100 lbs, barbell rowing 90 lbs, and overhead pressing 70 lbs. An added bonus is that my knees have never felt better. Should be fun to see where I&amp;rsquo;ll be at by the end of the year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Build a habit of doing yoga multiple times a week.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Do a mix of body weight exercises at home and weightlifting at the gym.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep biking and training for a century.&lt;/li>
&lt;/ul>
&lt;h3 id="digital-life">Digital Life&lt;/h3>
&lt;blockquote>
&lt;p>I find the digital world to be both a blessing and a curse, software itself is amoral but amplifies the underlying good or bad. On one hand I&amp;rsquo;ve never been more connected, empowered, and informed. On the other hand I&amp;rsquo;ve never felt so isolated, paralyzed, and confused. Not quite sure how to get off the treadmill or if I even want to, but the following seem like some rational first steps.&lt;/p>&lt;/blockquote>
&lt;p>Total bust so far. Besides becoming &lt;a href="/blog/productivity-porn/">more aware&lt;/a> of the downsides little has changed in my life. I need to spend more time researching and reflecting on how I can actually make improvements in this area of my life.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Avoid having my phone be the first thing I interact with in the morning.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Limit the empty calories I consume i.e. Instagram, Youtube, etc.&lt;/li>
&lt;/ul>
&lt;h3 id="finances">Finances&lt;/h3>
&lt;blockquote>
&lt;p>2021 was a big year for my finances: I read some great investing books; started investing in crypto; got my traditional investments in order; and built a big spreadsheet to help me keep track of my overall financial direction. I&amp;rsquo;d be happy if all I did in 2022 was continue to dollar cost average into my investments and keep my spreadsheet up to date — compounding will take care of the rest.&lt;/p>&lt;/blockquote>
&lt;p>I&amp;rsquo;m on track with my goals here and there isn&amp;rsquo;t much else to add. This is a big win.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep dollar cost averaging into passive index funds, ETH, BTC.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Keep my spreadsheet up to date.&lt;/li>
&lt;/ul>
&lt;h3 id="building">Building&lt;/h3>
&lt;blockquote>
&lt;p>Building things makes me happy. I don&amp;rsquo;t intend to be prescriptive about what I&amp;rsquo;ll build in 2022 — all that matters is that I build something.&lt;/p>&lt;/blockquote>
&lt;p>So far this year I&amp;rsquo;ve built two and a half projects the most exciting of which was StudyBuddy which took home 1st at the HackEd 2022 hackathon. We ended up trying and failing to turn StudyBuddy into a business opportunity but it was a great learning opportunity. I should write more about that experience. It would be fantastic if I could build one more solid project for the year.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Build and publish 2+ projects.&lt;/li>
&lt;/ul>
&lt;h3 id="reading">Reading&lt;/h3>
&lt;blockquote>
&lt;p>Last year I focused on reading less short/medium-form blog content and more long-form book content. Blogs have their place, but I felt that I was missing some of the nuance you find in books. I also wanted to rediscover fiction. It was a good start last year but I want to pick up the pace. The Kobo I got for christmas should hopefully assist with this.&lt;/p>&lt;/blockquote>
&lt;p>So far I&amp;rsquo;ve read about 6-7 books which is just under pace. In particular I enjoyed reading Andy Weir. The biggest detriment to my progress here has been my summer internship at Microsoft which has replaced all of my reading time with socializing time &amp;mdash; this is a tradeoff I&amp;rsquo;m happy to make. I still desire to read a lot more so I have big hopes for the fall.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 1.5+ books a month (for a total of 18+ over the year).&lt;/li>
&lt;/ul>
&lt;h3 id="writing">Writing&lt;/h3>
&lt;blockquote>
&lt;p>I tend to overthink (understatement of the year) so I should definitely be writing more. In the first year of this blog I managed to write eight posts. I chalk it up to a productive spurt in the early COVID lockdowns. In 2021 I only managed to write five. I think it&amp;rsquo;d be a big win if I wrote ten or more posts in 2022. Writing has compounding benefits so I want to start the compounding sooner than later.&lt;/p>&lt;/blockquote>
&lt;p>Having already written 11 posts (including this one) my expectations have been far exceeded. I have also started a meager email list that should hopefully grow over time. The wide range of topics that I&amp;rsquo;ve written about has helped to keep me interested in writing. I also have had some success with Hacker News. One post garnered &lt;a href="https://news.ycombinator.com/item?id=31007350">minor interest&lt;/a> and another made it to the &lt;a href="https://news.ycombinator.com/item?id=32335165">2nd spot&lt;/a> on the home page. This felt great!&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Publish 10+ blog posts.&lt;/li>
&lt;/ul>
&lt;h3 id="guitar">Guitar&lt;/h3>
&lt;blockquote>
&lt;p>Learning guitar was one of the best decisions I made in 2021 so I want to keep my foot on the gas pedal. The hardest thing here will be continuing to do focused practice; at the one year mark I have enough skill that I find it really easy to waste a whole practice session just noodling rather than working on the fundamentals.&lt;/p>&lt;/blockquote>
&lt;p>I am still loving guitar but I have struggled to find time for focused progress. With my summer internship over and hopefully some more free time at my disposal my goal is to reprioritize this. I want to avoid getting stuck at the intermediate level. One positive here is that I found and played with a jam buddy a couple of times. He was way better (understatement) than me and I learned a lot playing with him.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Beginner Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Ear Training and Theory up to Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep playing in front of family and friends to practice performing.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Find more people to jam with.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Record a cover of a song.&lt;/li>
&lt;/ul>
&lt;h3 id="web-30">Web 3.0&lt;/h3>
&lt;blockquote>
&lt;p>2021 was the year that I got red-pilled by crypto; I started buying ETH and BTC; I started mining HNT; but most of all I read and listened to a lot of crypto content. In 2022 I want to double down on actually tinkering around with crypto. I&amp;rsquo;m convinced that Web 3.0 is going to be huge so I want to get in early. The end goal of the tinkering is to discover what still needs to be built and to go build it.&lt;/p>&lt;/blockquote>
&lt;p>Besides continuing to dollar cost average into ETH and BTC I have made no progress here. Frankly I think that my passion here has just cooled off which I am more than okay with. I&amp;rsquo;m still convinced (even in the current bear market) that there will be a myriad of world changing use cases for crypto. I&amp;rsquo;m also still convinced that there is an adequate possibility of a highly asymmetric upside for owning crypto on a multi-decade time horizon. All that being said I currently have no desires to build or actively participate in the space. Besides investing I&amp;rsquo;ll still keep hosting Helium miners cause it is low effort and fun. I&amp;rsquo;d also still like to pick up some ENS domains.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Continue to dollar cost average my way into ETH and BTC.&lt;/li>
&lt;li>&lt;input checked="" disabled="" type="checkbox"> Purchase or host more Helium miners.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Register an ENS domain.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Try out some DeFi protocols.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Buy some NFTs and get involved with the communities.&lt;/li>
&lt;/ul>
&lt;h2 id="closing-thoughts">Closing Thoughts&lt;/h2>
&lt;p>Overall I&amp;rsquo;m pleased with how 2022 is going and I&amp;rsquo;m excited for what is to come. In doing this check-in I&amp;rsquo;ve come away with some observations about my methodology.&lt;/p>
&lt;blockquote>
&lt;p>Most people overestimate what they can do in one year and underestimate what they can do in ten years.
&lt;br>
— &lt;strong>Bill Gates&lt;/strong>&lt;/p>&lt;/blockquote>
&lt;p>First, I should be more ambitious with the goals I&amp;rsquo;m setting for myself. In some of the sections it felt like I was really just projecting what I was already going to inherently do rather than setting an ambitious goal to achieve. I&amp;rsquo;d rather aim high and miss in a few places than aim low and succeed.&lt;/p>
&lt;p>Second, I need to strike the right balance of being specific and metrics-driven versus abstract and aspirational. In areas like finances and writing it is a great idea to be metrics-driven. It is clear that I&amp;rsquo;ll be doing that exact work and being specific will help me drive towards meaningful goals. However, in areas like travel I should be less specific about my goals. It does me no good to say I&amp;rsquo;m going to travel somewhere and then just change my mind during the year. Rather, I should just aim to travel anywhere.&lt;/p>
&lt;p>Third, I want to spend more time reflecting on the categories I&amp;rsquo;m using. Are they at the right level of granularity? Am I missing any categories? Are private areas of my life being properly reflected upon? Lot&amp;rsquo;s of stuff to mull over here.&lt;/p>
&lt;p>See you in the new year.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Checkboxes will be ticked for anything that has been completely finished or is so clearly on track that I consider it complete. The latter statement of course leaves open the unfortunate possibility that I will regress in the coming six months and will need to uncheck that box. How awful.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>And you better believe that I&amp;rsquo;ll still be checking the Eastern Canada box off because a big trip is a big trip.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Productivity Porn</title><link>/blog/2022/productivity-porn/</link><pubDate>Tue, 28 Jun 2022 00:00:00 +0000</pubDate><guid>/blog/2022/productivity-porn/</guid><description>&lt;p>I have a pornography problem, but it&amp;rsquo;s not what you&amp;rsquo;re thinking. In fact, if you&amp;rsquo;re anything like me, you might too.&lt;/p>
&lt;p>It is well understood that social media can have a negative impact on your life. Whether you are doom-scrolling on Instagram, furiously retweeting on Twitter, or on your fifth hour of watching YouTube, you might experience:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Feelings of jealousy, anxiousness, and loneliness&lt;/strong> induced by only seeing heavily curated feeds of those you follow. This ultimately leads to unrealistic expectations about what your life &amp;ldquo;should&amp;rdquo; be like mixed with a constant bombardment of FOMO.&lt;/li>
&lt;li>&lt;strong>An increasingly polarized, misinformed, and enraged outlook on life&lt;/strong> as the incentives of our digital world push you deeper into a filter bubble, which might even lead to radicalization.&lt;/li>
&lt;li>&lt;strong>A crippling loss of time and agency&lt;/strong> caused by the addictive and distracting nature of these platforms that use the push notification as a phantasmal chain tethering us to our phone. This ends in you consuming content not because you truly want to but just &amp;mdash; because?&lt;/li>
&lt;/ul>
&lt;p>But, what I&amp;rsquo;ve recently come to realize is that there is an even more pernicious way in which social media is negatively impacting my life. Social media has become my productivity porn.&lt;/p>
&lt;p>Productivity porn is anything that after having been consumed makes you feel like you were productive when in reality you didn&amp;rsquo;t actually do anything. Just like regular pornography it stimulates you without even performing the act. It is a hollow imitation of the real thing.&lt;/p>
&lt;p>Examples of productivity porn include but are not limited to: reading a tweet by a top VC about how to become a better startup founder; watching a Youtube video about the 7 mistakes you need to avoid at the gym; perusing a Hacker News thread about how to improve the code you write. All of these activities deceptively make you feel like you&amp;rsquo;ve done something productive. &amp;ldquo;I just learned something new&amp;rdquo;, you tell yourself. And while this is true, you never actually did the thing you were setting out to do in the first place. A vicious cycle forms where you spend all your time thinking about doing things instead of actually doing them. As this cycle continues your productivity approaches zero.&lt;/p>
&lt;p>Keep in mind that productivity is a personal thing. It can look like a variety of things: founding a startup, working out at the gym, building a side project, reading a book, growing a garden. Regardless, the key is that you benefit from doing the actual activity, but not from thinking about doing it. Hence the problem with productivity porn.&lt;/p>
&lt;p>I only realized I had this addiction when it was pointed out to me over and over again. &lt;a href="https://robwalling.com/assets/ebook.pdf">Start Marketing The Day You Start Coding&lt;/a> by Rob Walling, &lt;a href="https://www.youtube.com/watch?v=M6NsEDwHHiE&amp;amp;t=512s&amp;amp;ab_channel=struthless">The drawing advice that changed my life&lt;/a> by struthless, &lt;a href="https://podcasts.apple.com/us/podcast/how-to-get-famous-on-twitter-and-other-listener-questions/id1469759170?i=1000558020601">How to get famous on twitter&lt;/a> by the My First Million podcast, and &lt;a href="https://www.youtube.com/watch?v=gYwkKaK5yeQ&amp;amp;ab_channel=InternetShaquille">The Cooking Skill That Can&amp;rsquo;t Be Taught&lt;/a> by Internet Shaquille all were telling me the message I needed to hear. They each said it in a different way, but the core of their message was the same. Stop thinking (reading, listening, watching etc.) about how to do something and just go do it. Or using my terminology, they were all saying that I was addicted to productivity porn&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>Each time I&amp;rsquo;d hear their message I would be hit with an electric shock when I realized that they were right. I would immediately shut off my phone. For about a week after I would steer clear of any and all productivity porn, but inevitably I would find it weaseling its way back into my life.&lt;/p>
&lt;p>I find productivity porn to be a uniquely terrifying negative effect of social media for four reasons. While these reasons are also relevant to the other negative impacts of social media, I think they are particularly fitting for productivity porn.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>It saps my intrinsic motivation.&lt;/strong> When I find myself wasting excessive amounts of time on social media my intrinsic motivation normally acts as a backstop. Eventually I will become tired of scrolling on social media and will go do something productive. However, productivity porn short circuits this by making me think I was being productive the entire time.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>It is very deceptive.&lt;/strong> As far as I can tell I&amp;rsquo;ve been negatively impacted by this since at least highschool and I&amp;rsquo;m only realizing it right now. Looking back, it&amp;rsquo;s alarming to think about how much time I have spent actually doing things versus just thinking about doing them.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>It is necessary in moderation.&lt;/strong> Unlike other parts of social media, you actually need some of this in moderation. It is equally as bad to only ever do things and to never spend time learning about how to do them better. This makes avoiding it difficult because you can&amp;rsquo;t just cut it out cold turkey.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>It is found in all forms of media.&lt;/strong> This is the scariest reason for me. Typically when I feel like social media is too negative for me I feel safe running away to slower, less connected, and more deliberate forms of media &amp;mdash; books, podcasts, blog posts, etc. These lack the virality of &lt;em>social&lt;/em> media platforms and mitigate the issues that arise. However, I find productivity porn is just as prevalent in traditional media as it is in social media. While perhaps it is more toned down when found in traditional media, there is still no safe haven from productivity porn.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;ve spent a lot of time thinking about how to conclude this post and I don&amp;rsquo;t think I&amp;rsquo;ve come up with anything satisfying. The only thing that is clear to me is that it is abundantly important that I figure out how to stymie the negative effect that productivity porn is having on my life. When I look for solutions to this problem I only come up with half-baked ideas and more questions. Is this something you&amp;rsquo;ve encountered in your life? What have you done about it?&lt;/p>
&lt;hr>
&lt;p>&lt;em>Thanks to Morgan Frisby for reviewing drafts of this.&lt;/em>&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Ironically, the thing delivering the wake-up message was productivity porn itself self-aware enough to surface the issue.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>When Should I Give Up?</title><link>/blog/2022/when-should-i-give-up/</link><pubDate>Mon, 30 May 2022 00:00:00 +0000</pubDate><guid>/blog/2022/when-should-i-give-up/</guid><description>&lt;p>When should I give up on something and when should I see it through?&lt;/p>
&lt;p>I&amp;rsquo;m asking this question after my third attempt this week at writing a blog post I&amp;rsquo;ve been thinking about writing for the past year.&lt;/p>
&lt;p>The idea for the blog post seems so promising when I think about it. I&amp;rsquo;m able to imagine it in its fully finished form and I can picture the effect it would have on the reader.&lt;/p>
&lt;p>But the words don&amp;rsquo;t flow when I sit down to write it. I can&amp;rsquo;t seem to thread a cohesive narrative for the reader that is engaging and educative at the same time.&lt;/p>
&lt;p>This doesn&amp;rsquo;t always happen to me. Sometimes when I sit down to write a blog post the words seem to just pour out of me as if they desire to escape my head and be on the page.&lt;/p>
&lt;p>This mental block isn&amp;rsquo;t unique to writing blog posts. I&amp;rsquo;ll run into it any time I&amp;rsquo;m doing something that requires creativity and isn&amp;rsquo;t just rote execution. Like when I&amp;rsquo;m building a side project and I just can&amp;rsquo;t structure the code in a way that makes sense. Or when I&amp;rsquo;m pursuing a business idea and it just seems like no one cares and I&amp;rsquo;m not actually solving a problem.&lt;/p>
&lt;p>Should I always expect it to be easy? Is getting blocked my subconscious&amp;rsquo;s way of saying that I&amp;rsquo;m working on a bad idea and it isn&amp;rsquo;t worth my time? Or, on the other extreme should I always try to push through these mental blocks? The idea being that struggle is a prerequisite for doing something great.&lt;/p>
&lt;p>Both of these diametrically opposed positions feel wrong to me. I can think of examples on both sides where giving up or pushing through were the right call. It scares me to waste my life working on things that will never succeed. Yet I&amp;rsquo;m equally scared of spending my whole life bouncing between projects without conviction and thus never creating something meaningful.&lt;/p>
&lt;p>I don&amp;rsquo;t think that there are any right answers here. And if there are they lie in a blurry middle ground that is constantly shifting. My intuition says that knowing when to give up or keep going is part of the craft. Going forward, when I feel blocked I&amp;rsquo;m going to try and ask myself the following questions to see if I should give up or keep going.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Am I blocked for internal (I can&amp;rsquo;t do it) or external (no one wants it) reasons?&lt;/strong>&lt;/p>
&lt;p>This is a hard thing to figure out but very insightful. Internal blockers are probably best pressed through. External blockers are often an indicator that what I&amp;rsquo;m doing isn&amp;rsquo;t worth it. Fair warning though that the greatest innovations often come from pushing through external blockers e.g. basically every new technological innovation.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>How many times have I tried?&lt;/strong>&lt;/p>
&lt;p>If I&amp;rsquo;ve only tried something once or twice, maybe I just haven&amp;rsquo;t tried hard enough and should give it another go. What&amp;rsquo;s the upper limit on the right number of tries? Is this proportional to the difficulty of the project?&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>How passionate about this am I?&lt;/strong>&lt;/p>
&lt;p>If I&amp;rsquo;m not passionate about something it isn&amp;rsquo;t worth doing even if I succeed. In the startup world this is often called product-founder fit.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>What am I doing instead?&lt;/strong>&lt;/p>
&lt;p>Is the alternative to pushing through the mental block sitting on the couch and doing nothing? I should probably keep working. Is the alternative working on a new idea that I can&amp;rsquo;t stop thinking about? Maybe it&amp;rsquo;s a good idea to go pursue it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>What have I been doing too much of?&lt;/strong>&lt;/p>
&lt;p>If I&amp;rsquo;ve given up on the past five things I&amp;rsquo;ve tried, then maybe it&amp;rsquo;s time to stick it through on one. The inverse is also true.&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>Chrome Shortcuts I Use Everyday</title><link>/blog/2022/chrome-shortcuts-i-use-everyday/</link><pubDate>Tue, 17 May 2022 00:00:00 +0000</pubDate><guid>/blog/2022/chrome-shortcuts-i-use-everyday/</guid><description>&lt;p>My favorite web browser is Google Chrome. I want to share with you some of the shortcuts that I use everyday. It&amp;rsquo;s too dramatic to say that I couldn&amp;rsquo;t live without them, but, it is accurate to say that they make my life &lt;strong>way&lt;/strong> better.&lt;/p>
&lt;p>You&amp;rsquo;re going to notice lots of these shortcuts are only avoiding relatively easy mouse movements. For example pressing &lt;code>CTRL&lt;/code> + &lt;code>T&lt;/code> instead of moving your mouse to the plus sign for new tabs and clicking it. At first glance this optimization seems like overkill. However, when you consider that most knowledge workers spend hours of their day in a browser the math starts to add up on the time saved.&lt;/p>
&lt;h2 id="opening-and-closing-tabs">Opening and closing tabs&lt;/h2>
&lt;p>Press &lt;code>Ctrl&lt;/code> + &lt;code>t&lt;/code> to open a new tab. Press &lt;code>Ctrl&lt;/code> + &lt;code>w&lt;/code> to close the current tab.&lt;/p>
&lt;p>Of all the shortcuts in Chrome, I easily use these the most. Like everyone else I always have a million tabs open. This saves me from constantly moving my mouse up to the top of the browser to open or close tabs.&lt;/p>
&lt;h2 id="new-window--new-incognito">New window / new incognito&lt;/h2>
&lt;p>Press &lt;code>Ctrl&lt;/code> + &lt;code>n&lt;/code> to open a new window. Press &lt;code>Ctrl&lt;/code> + &lt;code>Shift&lt;/code> + &lt;code>n&lt;/code> to open a new incognito tab.&lt;/p>
&lt;p>Sometimes you need to open a new window instead of just a tab. For example, I&amp;rsquo;ll often put some reference documentation up in a different window that I can drag on to my second monitor. Also, as a developer easily being able to pull up an incognito window is great for testing. Incognito windows start from a clean state and make it easier to test things like cookies.&lt;/p>
&lt;h2 id="reopen-tabs">Reopen tabs&lt;/h2>
&lt;p>Press &lt;code>Ctrl&lt;/code> + &lt;code>Shift&lt;/code> + &lt;code>t&lt;/code> to reopen previously closed tabs in the order they were closed.&lt;/p>
&lt;p>This is my favorite shortcut &amp;mdash; it was a total game changer when I learned about it. With this shortcut you no longer need to worry about painstakingly reopening all your tabs when you accidentally close your Chrome window.&lt;/p>
&lt;h2 id="navigate-through-tabs">Navigate through tabs&lt;/h2>
&lt;p>Press &lt;code>Ctrl&lt;/code> + &lt;code>Tab&lt;/code> or &lt;code>Ctrl&lt;/code> + &lt;code>Shift&lt;/code> + &lt;code>Tab&lt;/code> to navigate through your open tabs.&lt;/p>
&lt;p>I&amp;rsquo;ll often use this when I have a number of tabs that I need to flip through to compare and I don&amp;rsquo;t have enough screen real estate to put them all side-by-side.&lt;/p>
&lt;h2 id="open-previous-or-next-page">Open previous or next page&lt;/h2>
&lt;p>Press &lt;code>Alt&lt;/code> + &lt;code>Left Arrow&lt;/code> to open the previous page. Press &lt;code>Alt&lt;/code> + &lt;code>Right Arrow&lt;/code> to open the next page.&lt;/p>
&lt;p>You save a surprising amount of time using these shortcuts instead of always clicking the back button. Especially when you&amp;rsquo;re in a googling frenzy and peaking into different search results as you try to answer your question.&lt;/p>
&lt;h2 id="search-on-page">Search on page&lt;/h2>
&lt;p>Press &lt;code>Ctrl&lt;/code> + &lt;code>f&lt;/code> to bring up a search bar. Type your search term into the search bar. Use &lt;code>Enter&lt;/code> and &lt;code>Shift&lt;/code> + &lt;code>Enter&lt;/code> to navigate through the search results.&lt;/p>
&lt;p>Pretty hard to imagine that you don&amp;rsquo;t know this one. But, using this can really save you a lot of time. Anytime you&amp;rsquo;re scrolling for something you know is on the page try searching for it.&lt;/p>
&lt;h2 id="jump-to-address-bar">Jump to address bar&lt;/h2>
&lt;p>Press &lt;code>Ctrl&lt;/code> + &lt;code>l&lt;/code> to jump to the address bar.&lt;/p>
&lt;p>Another one I use all the time. With Google built into the address bar this shortcut is basically my &amp;ldquo;Google it&amp;rdquo; shortcut.&lt;/p>
&lt;h2 id="ctrl-clicking-links">Ctrl clicking links&lt;/h2>
&lt;p>I love opening links in new tabs. Combined with the shortcuts to navigate between tabs this is really powerful.&lt;/p>
&lt;h2 id="developer-tools">Developer tools&lt;/h2>
&lt;p>Press &lt;code>F12&lt;/code> to open the developer tools. Press &lt;code>Ctrl&lt;/code> + &lt;code>Shift&lt;/code> + &lt;code>c&lt;/code> and click on the webpage somewhere to bring up the DOM focused on that element.&lt;/p>
&lt;p>These ones are handy if you do lots of web development. Stop bothering to right click to get into the developer tools.&lt;/p>
&lt;h2 id="hard-refresh">Hard refresh&lt;/h2>
&lt;p>Press &lt;code>Ctrl&lt;/code> + &lt;code>Shift&lt;/code> + &lt;code>r&lt;/code> to perform a hard refresh.&lt;/p>
&lt;p>Another pro tip for web developers. If you want to reload everything (don&amp;rsquo;t cache CSS etc.) you can do a hard refresh.&lt;/p>
&lt;h2 id="bonus-tab-through-search-results">Bonus: Tab through search results&lt;/h2>
&lt;p>Press &lt;code>Tab&lt;/code> to cycle through Google search results. Press &lt;code>Enter&lt;/code> to navigate to a search result.&lt;/p>
&lt;p>If you install &lt;a href="https://chrome.google.com/webstore/detail/hit-tab-and-enter-on-goog/kkldgaaaafjoipnomoinnkccihdiffee?hl=en">this extension&lt;/a> then you can tab through Google search results. This has exponentially sped up my googling. I can keep my hands on the keyboard for an entire Google search.&lt;/p></description></item><item><title>6 Tips for Starting a Software Engineering Internship</title><link>/blog/2022/6-tips-for-starting-a-software-engineering-internship/</link><pubDate>Sun, 01 May 2022 00:00:00 +0000</pubDate><guid>/blog/2022/6-tips-for-starting-a-software-engineering-internship/</guid><description>&lt;p>On Monday I&amp;rsquo;ll be starting my third internship with Microsoft, and my fifth internship in total. Reflecting on my previous internships and preparing for the coming one I came up with six tips that have helped me in my internships&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;h2 id="1-ask-lots-of-questions">1. Ask lots of questions&lt;/h2>
&lt;p>This very well might be the cardinal rule for interns. It can be scary to ask questions because you don&amp;rsquo;t want to show that you don&amp;rsquo;t know something. Bad. Fight that impulse. Everyone understands that interns don&amp;rsquo;t know a lot of things. If they wanted someone who already knew everything they would not have hired you. What they&amp;rsquo;re looking for is someone who can learn quickly and level up. Questions are an essential step in that process.&lt;/p>
&lt;p>Ask questions about everything. Why am I getting this error when I run the app? How do I test this code I just wrote? Why are we using Python instead of Go? What should my pull request look like? What part of the codebase is Sheila responsible for?&lt;/p>
&lt;p>There is no such thing as a bad question. However, there are better questions. Make sure you&amp;rsquo;ve tried to answer the question yourself with a quick search on google or the company wiki. And be respectful of people&amp;rsquo;s time, make sure that you&amp;rsquo;re not interrupting people so often that they can&amp;rsquo;t focus&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.&lt;/p>
&lt;h2 id="2-talk-to-everyone">2. Talk to everyone&lt;/h2>
&lt;p>Typically in an internship you&amp;rsquo;ll spend lots of time talking with your manager and mentor. But, I suggest you make an effort to talk with a lot of other people too. A conversation with your skip level manager can give you a better idea of the big picture. Talking to other engineers or product managers on your team will give you more context. And I even recommend you reach out to people outside your team as a great networking opportunity.&lt;/p>
&lt;p>As an intern you can pretty much ask anyone to talk and they&amp;rsquo;re likely to say yes. Make sure you come prepared with questions you want to ask. If it doesn&amp;rsquo;t go well then don&amp;rsquo;t sweat it. Not every conversation you have will be perfect.&lt;/p>
&lt;h2 id="3-understand-the-big-picture">3. Understand the big picture&lt;/h2>
&lt;p>I think that understanding the big picture is drastically underrated on internships. Doing this can set you apart from being a good intern to a great intern. By understanding the big picture I mean that you should make an effort to understand the &lt;strong>why&lt;/strong>, not just the &lt;strong>what&lt;/strong> or &lt;strong>how&lt;/strong>. Why is the software I&amp;rsquo;m working on relevant to the business. Why are customers buying our product? Why do we need to solve this problem with software? And so on &amp;mdash; you get the point.&lt;/p>
&lt;p>You will be able to make much better decisions by taking the time to look up and understand the world around the software you&amp;rsquo;re working on. For example maybe it&amp;rsquo;s really important that spend that extra time to polish the styling because you learned that a key reason why customers switch to the product is a more intuitive UX. I also find that understanding the context around why I&amp;rsquo;m building something motivates me even more.&lt;/p>
&lt;h2 id="4-focus-on-the-details">4. Focus on the details&lt;/h2>
&lt;p>The big picture is important, but so is the minutia. Make an effort to really understand the small area of the code that you&amp;rsquo;ll be working on. Try to become an expert on it. This will pay dividends as you work on your project.&lt;/p>
&lt;p>Don&amp;rsquo;t worry about the details of anything adjacent to what you work on; understand how the pieces fit together but save learning the details of those systems for later. Internships are so short that you only will have time to go deep on a small part of the whole.&lt;/p>
&lt;h2 id="5-write-stuff-down">5. Write stuff down&lt;/h2>
&lt;p>You&amp;rsquo;re going to have a lot of information thrown at you as you start your internship. Take the time to write the important bits down. Stuff like that obscure command you need to run to startup the database, or the list of important domain objects you should learn. These notes will help you from constantly needing to nag your teammates to ask them the same thing over and over.&lt;/p>
&lt;h2 id="6-ship-code-as-quick-as-you-can">6. Ship code as quick as you can&lt;/h2>
&lt;p>Shipping code is the strongest signal that you are meaningfully contributing to the team and going to be a good fit. You want to do this as quickly as possible. At a startup that might mean you&amp;rsquo;re pushing a small PR on the very first day &amp;mdash; something like a bug fix or documentation. At larger companies a more reasonable goal is in the first week or two.&lt;/p>
&lt;p>A good leading indicator for how quickly you can ship code is how quickly you can clone and run the codebase. Ideally you&amp;rsquo;ve got it running in the first few hours at a startup or in the first few days at a large company. You&amp;rsquo;ll probably run into lots of issues while trying to run the code; this is a perfect opportunity for a quick PR updating the README.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>While this post has been written on the basis of internships rather than full-time positions, I still think that all of the tips hold up. More broadly these tips are about how to do well in the first few months of a software engineering position.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>I&amp;rsquo;m breezing over this here as if the tradeoff of how much to interrupt people is simple. But, it&amp;rsquo;s actually really difficult to find the right balance. I should write more about this in the future.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Stating the Obvious</title><link>/blog/2022/stating-the-obvious/</link><pubDate>Fri, 22 Apr 2022 00:00:00 +0000</pubDate><guid>/blog/2022/stating-the-obvious/</guid><description>&lt;p>A common excuse people use for not writing is that they don&amp;rsquo;t have anything insightful to say. Or in other words they don&amp;rsquo;t want to state the obvious. I think this is a poor excuse and that more people should write about obvious things&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;p>Setting aside the likelihood that you do have insightful things to share, here are three reasons why stating the obvious is a good idea.&lt;/p>
&lt;p>First, obviousness is a relative concept, not absolute. This means that what is obvious to you is not necessarily obvious to others. The clearest example of this is your past self. For example, it is obvious to me what the differences are between static and dynamically typed programming languages &amp;mdash; but this wasn&amp;rsquo;t obvious to me a few years ago before I had experience programming. Topics like this are a sweet spot for teaching people by accident. One man&amp;rsquo;s obvious observation is another man&amp;rsquo;s clear and insightful tutorial.&lt;/p>
&lt;p>Second, you often need to work through a lot of obvious concepts in order to arrive at something insightful. Put another way, insight comes from chaining together obvious components into a novel order or structure. It&amp;rsquo;s sort of like mathematics. You can start with obvious concepts like addition and build them up into more complex concepts like multiplication or exponentiation.&lt;/p>
&lt;p>Third, insight is not the end all be all of writing. There are a plethora of reasons to write besides communicating a novel insight: to entertain the reader with a story; to improve in the craft of writing and communicating your ideas (regardless of their quality); to record you perspective at a point in time; and many more.&lt;/p>
&lt;p>If you think something is obvious then maybe you should write about. I would love to hear what you have to say.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Inspiration for this post came from Dynomight&amp;rsquo;s post on &lt;a href="https://dynomight.net/training/">creative nonfiction training exercises&lt;/a>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Three Things You Should Buy and Use</title><link>/blog/2022/three-things-you-should-buy-and-use/</link><pubDate>Sun, 10 Apr 2022 00:00:00 +0000</pubDate><guid>/blog/2022/three-things-you-should-buy-and-use/</guid><description>&lt;p>Inspired by Dynomight&amp;rsquo;s list of &lt;a href="https://dynomight.net/training/">creative nonfiction training exercises&lt;/a>, here is a list of three things that I think you should buy and use.&lt;/p>
&lt;h1 id="password-manager">Password Manager&lt;/h1>
&lt;p>It took me way too long to get on this train, but using a password manager has removed so many problems from my digital life:&lt;/p>
&lt;ul>
&lt;li>Trying to think of a new password when creating an account&lt;/li>
&lt;li>Trying to remember which variation of a password I used on a given site (did it begin with a capital or end with a 1?)&lt;/li>
&lt;li>Still being incredibly insecure because I&amp;rsquo;m constantly reusing the same password&lt;/li>
&lt;/ul>
&lt;p>I can&amp;rsquo;t recommend a password manager strongly enough. The benefit is clear: you&amp;rsquo;ll reduce your cognitive overhead to a single password and greatly increase your security at the same time.&lt;/p>
&lt;p>The thing about password managers is that it doesn&amp;rsquo;t really matter which one you use. Using a password manager is so much better than not using one that the minute differences between each are almost irrelevant. That being said, I use &lt;a href="https://1password.com/">1Password&lt;/a> and recommend you do too. I started out using &lt;a href="https://www.lastpass.com/">LastPass&lt;/a> but found the user interface unsatisfactory. 1Password is really pleasant to use and is reasonably priced.&lt;/p>
&lt;p>You need to keep two things in mind to have the most success with password managers. First, you need to pay for it. Your password manager is going to be the foundation of your digital security &amp;mdash; don&amp;rsquo;t leave that up to a free plan. Second, commit to it 100%. That means you need to put every password into it&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> and you need to change every password to a securely generated and unique password. If you&amp;rsquo;ll keep these two things in mind you&amp;rsquo;ll have a great time.&lt;/p>
&lt;h1 id="ereader">eReader&lt;/h1>
&lt;p>In the past year I&amp;rsquo;ve endeavoured to &lt;a href="https://calebschoepp.com/blog/2021/year-in-review-2021/#reading">read more&lt;/a>. In pursuit of this goal I picked up an eReader and I was pleasantly surprised with how much I liked it. Here&amp;rsquo;s some of the key benefits of using an eReader:&lt;/p>
&lt;ul>
&lt;li>The device is very small so it&amp;rsquo;s easy to throw in a backpack or travel with&lt;/li>
&lt;li>You can &amp;ldquo;carry&amp;rdquo; as many books with you as you want so you&amp;rsquo;ll always have something to read that you&amp;rsquo;re interested in&lt;/li>
&lt;li>The screen is much more tolerable on the eyes compared to your phone&lt;/li>
&lt;li>You can change the font size as you desire&lt;/li>
&lt;li>There are all sorts of fun reading stats to explore&lt;/li>
&lt;/ul>
&lt;p>I&amp;rsquo;m sure that there are some people who are sticklers for the &amp;ldquo;feel of a real book&amp;rdquo;. Perhaps an eReader won&amp;rsquo;t work for them. But, I think that most people will stop missing that almost immediately &amp;mdash; I certainly did.&lt;/p>
&lt;p>The eReader I ended up buying was a &lt;a href="https://www.amazon.ca/Kobo-Clara-6IN-EPD-Carta/dp/B07CXNMVKY">Kobo Clara HD&lt;/a>. It was the right price and integrated with my local library. Chances are if you live in Canada this device would also be a great option for you. For any Americans reading this you may want to look into a Kindle.&lt;/p>
&lt;h1 id="white-noise-machine">White Noise Machine&lt;/h1>
&lt;p>It seems like some people can fall asleep on command. That&amp;rsquo;s not me. Falling asleep for me can be a long process. Sometimes it feels like I just don&amp;rsquo;t know how to shut my brain down. On the encouragement of my brother I ended up purchasing a &lt;a href="https://www.amazon.ca/Adaptive-Technologies-ASM1007-Lectrofan-Machine/dp/B00E6D6LQY">white noise machine&lt;/a>. What a game changer! I&amp;rsquo;ve been sleeping much better since I started using it.&lt;/p>
&lt;p>I think the white noise is helping me sleep in two ways. First, it helps drown out any small background noises. My brain focuses in on any repetitive background noises and it can keep me up. With a white noise machine I no longer hear these problematic noises. Second, something about the white noise calms me down and slows my thoughts. It&amp;rsquo;s almost meditative.&lt;/p>
&lt;p>This all might sound a bit over the top, but I think that small optimizations to my sleep are totally worth it. Tonight&amp;rsquo;s sleep is a big determiner of the quality of tomorrow. If you aren&amp;rsquo;t the best sleeper I recommend you try getting a white noise machine.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Yes, even your most cherished passwords like your Google or Facebook accounts. For that matter, I recommend you put &lt;strong>everything&lt;/strong> in your password manager. I keep my credit cards and important documents in my password manager too.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Discovering Blogs</title><link>/blog/2022/discovering-blogs/</link><pubDate>Tue, 05 Apr 2022 00:00:00 +0000</pubDate><guid>/blog/2022/discovering-blogs/</guid><description>&lt;p>One of my favorite things is when I discover a new blog.&lt;/p>
&lt;p>How I find the new blog always varies. I might find it while hunting for the answer to a specific technical question. Perhaps it&amp;rsquo;s been linked to from social media or newsletters I subscribe to. Or possibly I follow a link their from another blog.&lt;/p>
&lt;p>But, if that first post is any good, what I do next is always the same. I find their archive of blog posts and I binge. You can bet that I&amp;rsquo;ll be &lt;a href="https://www.computerhope.com/jargon/c/ctrl-click.htm#open-links-tab">control clicking&lt;/a> on any title that sounds even remotely interesting.&lt;/p>
&lt;p>Some of the time what I find is disappointing and I quickly move on. Other times they&amp;rsquo;ve got some good posts and I&amp;rsquo;ll stick around for a bit and read. Every once and awhile though, it all just clicks. Something about the way that this person writes just aligns with the way that I think. It&amp;rsquo;s magical when this happens. These are the blogs that go in my RSS feed.&lt;/p>
&lt;p>I have very vivid memories of discovering certain blogs. As a young teenager I discovered &lt;a href="http://www.paulgraham.com/">Paul Graham&lt;/a> and read nearly everything he ever wrote. His writing was incredibly formative for me. Early in my university years I discovered &lt;a href="http://steve-yegge.blogspot.com/">Steve Yegge&lt;/a>. It was a revelation to read such funny writing about software engineering.&lt;/p>
&lt;p>In the past week I&amp;rsquo;ve discovered two new blogs. A blog called &lt;a href="https://dynomight.net/">Dynomight&lt;/a> and my friend &lt;a href="https://www.notion.so/8aa813b0cf914c3c842e78e38cc9d675?v=8b3e285b9a164586a36775ee4e574884">Mitch&amp;rsquo;s blog&lt;/a>. Posts from both of these blogs inspired me to write this. If you like what I write then you might like what they write too.&lt;/p>
&lt;p>I&amp;rsquo;d love to hear about the new blogs that you&amp;rsquo;ve discovered. Drop me an email!&lt;/p></description></item><item><title>Skating My Way to a New Mindset</title><link>/blog/2022/skating-my-way-to-a-new-mindset/</link><pubDate>Fri, 25 Mar 2022 00:00:00 +0000</pubDate><guid>/blog/2022/skating-my-way-to-a-new-mindset/</guid><description>&lt;p>One of the things you quickly learn as an employee at Microsoft is that the company is really big on the idea of a &amp;ldquo;growth mindset&amp;rdquo;. This platitude is everywhere — employee training, corporate messaging, employee reviews.&lt;/p>
&lt;p>I initially found this messaging quite abrasive. I was allergic to a company preaching about how I should think. In an ironic display of a fixed mindset (the opposite of a growth mindset) I viewed this ulterior mode of thinking as a threat, instead of as the opportunity it is.&lt;/p>
&lt;p>It wasn&amp;rsquo;t until this year that I internalized the true meaning and power of a growth mindset. If you&amp;rsquo;ll believe it, learning to skate of all things was what opened my mind to the idea of a growth mindset.&lt;/p>
&lt;p>I was born and raised in small town Nebraska. My family lived there until I was five at which point we moved to Canada for my Dad&amp;rsquo;s work. The running joke in my family is that in an alternate universe I would be a Corn Husker linebacker attending the University of Nebraska-Lincoln.&lt;/p>
&lt;p>For reasons I can&amp;rsquo;t explain I never learned to skate growing up. Things must have just never lined up. I was always the kid running around on the ice in his boots when the class went on a skating field trip. As I grew older and more self-conscious, my inability to skate began to morph into my great Canadian shame.&lt;/p>
&lt;p>After a decade of being embarrassed about not being able to skate it had become a part of my identity. It sounds dramatic, but, it&amp;rsquo;s true. I was just the silly American who didn&amp;rsquo;t know how to skate and who could never learn. That&amp;rsquo;s just the way it was. That&amp;rsquo;s just who I was.&lt;/p>
&lt;p>In a twist of fate, for the first time in years the outdoor rink by my house was being maintained. A saint of a gentleman from the neighborhood was carefully flooding the rink and keeping the shack open every night. Pristine ice a two minute walk from my house — what a waste!&lt;/p>
&lt;p>On the encouragement of my family I made a move that was out of character for me. I bought some skates, laced up, and hit the rink. I was bad. Really bad. But, something happened that I didn&amp;rsquo;t expect. I had a really good time. So I kept going back to the rink and I started getting better. Although I&amp;rsquo;m sure the idea that skating would make you a better skater is unsurprising to the reader. Let me assure you that this was surprising to me — the silly American who could never learn to skate.&lt;/p>
&lt;p>What started as going to the rink once a week turned into going to the rink every night. I started playing shinny with people from the neighborhood. I started to learn to crossover and skate backwards. I wasn&amp;rsquo;t very good — nine year olds could still beat me in a 1-1 game of hockey — but I could skate!&lt;/p>
&lt;p>My inability to skate had always been a part of my identity. A shameful and unhelpful part of my identity, but, a part of it all the same. I viewed learning to skate as an attack on my identity. Or in other words, my fixed mindset told me that skills are innate and unlearnable. I can&amp;rsquo;t skate thus I&amp;rsquo;ll never be able to skate.&lt;/p>
&lt;p>Learning to skate has shown me how wrong I was. Who you are can and should change. More importantly just because you don&amp;rsquo;t know how to do something right now doesn&amp;rsquo;t mean that you will never be able to do it.&lt;/p>
&lt;p>Ever since I&amp;rsquo;ve learned to skate I&amp;rsquo;ve been itching to find other areas of my life where a fixed mindset is artificially limiting me. Where is a fixed mindset holding you back?&lt;/p></description></item><item><title>I Want to Be an Entrepreneur so That I Can...</title><link>/blog/2022/i-want-to-be-an-entrepreneur-so-that-i-can.../</link><pubDate>Sat, 12 Feb 2022 00:00:00 +0000</pubDate><guid>/blog/2022/i-want-to-be-an-entrepreneur-so-that-i-can.../</guid><description>&lt;h3 id="be-involved-in-the-entire-process">Be Involved in the Entire Process&lt;/h3>
&lt;p>I love building software, but, it&amp;rsquo;s never quite felt like enough. I&amp;rsquo;m not happy just being a cog in the machine shipping widgets. I want to be involved in the product design, marketing, support, business decisions etc.&lt;/p>
&lt;h3 id="have-more-flexibility">Have More Flexibility&lt;/h3>
&lt;p>Working for yourself can give you the freedom to construct a life you want to live. Ironically by constraining what you&amp;rsquo;re willing to build you can introduce more flexibility into your life.&lt;/p>
&lt;h3 id="have-greater-financial-leverage">Have Greater Financial Leverage&lt;/h3>
&lt;p>You can make good money as a software engineer, but, the reality is that you capture very little of the value you create. When you own what you&amp;rsquo;re building you take on more risk in exchange for the possibility of compounding growth.&lt;/p>
&lt;hr>
&lt;p>Thank you to &lt;a href="https://justinjackson.ca/">Justin Jackson&lt;/a> for &lt;a href="https://youtu.be/TnWo6ex1C4E?t=638">inspiring me&lt;/a> to think about this. If you&amp;rsquo;re someone starting their entrepreneurial journey like I am I encourage you to think about this too.&lt;/p></description></item><item><title>Building StudyBuddy at HackED 2022</title><link>/blog/2022/building-studybuddy-at-hacked-2022/</link><pubDate>Mon, 24 Jan 2022 00:00:00 +0000</pubDate><guid>/blog/2022/building-studybuddy-at-hacked-2022/</guid><description>&lt;p>This past weekend I participated in &lt;a href="https://hacked-2022.devpost.com/">HackED 2022&lt;/a>, the annual hackathon put on by the U of A Computer Engineering club. My teammates Armi, Cyrus, Ryan, and I built a web app called StudyBuddy and we finished in first place winning a $1000 grand prize. I wanted to reflect on the experience like I did &lt;a href="/blog/2021/building-grokjs-at-hacked-2021">last year&lt;/a>.&lt;/p>
&lt;h2 id="demo">Demo&lt;/h2>
&lt;p>Before I go over the story in a chronological order I want to share a demo of what we built.&lt;/p>
&lt;p>StudyBuddy is a web app designed to help create quizzes (with answers) based on course content. Students can use the quizzes to study and teachers can use the quizzes to create new material. Using AI we made the process of creating a quiz as easy as uploading a PDF.&lt;/p>
&lt;p>To get started you can make an account on StudyBuddy and login.&lt;/p>
&lt;p>&lt;a href="/studybuddy/login.png">&lt;img src="/studybuddy/login.png" alt="Login screen of StudyBuddy">&lt;/a>&lt;/p>
&lt;p>This will bring you to the dashboard.&lt;/p>
&lt;p>&lt;a href="/studybuddy/dashboard_empty.png">&lt;img src="/studybuddy/dashboard_empty.png" alt="Dashboard screen of StudyBuddy">&lt;/a>&lt;/p>
&lt;p>From here you can get started by uploading a PDF as some new content. We found that StudyBuddy worked well with lots of different content types — lecture slides, class notes, textbook pages, etc. Give the content a title and some tags, then go ahead and click &lt;strong>Create Content&lt;/strong>.&lt;/p>
&lt;p>&lt;a href="/studybuddy/new_content.png">&lt;img src="/studybuddy/new_content.png" alt="New content screen of StudyBuddy">&lt;/a>&lt;/p>
&lt;p>The detail page for a piece of content does a few things. At the top of the page it provides a preview of the PDF you uploaded.&lt;/p>
&lt;p>&lt;a href="/studybuddy/detail_1.png">&lt;img src="/studybuddy/detail_1.png" alt="Detail screen one of StudyBuddy">&lt;/a>&lt;/p>
&lt;p>Below the preview is a list of questions that were automatically generated for the content when you uploaded it. By default one question is generated for each page, up to a maximum of 20. Each question block shows the question and its associated answer. If you don&amp;rsquo;t like a question you can delete it. You can also use the &lt;strong>Add Question&lt;/strong> button to add more questions.&lt;/p>
&lt;p>&lt;a href="/studybuddy/detail_2.png">&lt;img src="/studybuddy/detail_2.png" alt="Detail screen two of StudyBuddy">&lt;/a>&lt;/p>
&lt;p>Back at the top of the detail page, if you click &lt;strong>Quiz Me&lt;/strong> you will be taken to a new screen that shows all of the content&amp;rsquo;s questions in random order with the answers hidden. To reveal the questions you can click on the cards.&lt;/p>
&lt;p>&lt;a href="/studybuddy/quiz.png">&lt;img src="/studybuddy/quiz.png" alt="Quiz screen of StudyBuddy">&lt;/a>&lt;/p>
&lt;p>If you click the &lt;strong>Quiz Me&lt;/strong> button that is found on the dashboard you&amp;rsquo;ll be brought to a special quiz creation screen.&lt;/p>
&lt;p>&lt;a href="/studybuddy/dashboard_with_content.png">&lt;img src="/studybuddy/dashboard_with_content.png" alt="Dashboard of StudyBuddy">&lt;/a>&lt;/p>
&lt;p>Here you can select multiple pieces of content that you would like to generate a quiz about. You can even select content based on the tags you gave them. When you click &lt;strong>Create Quiz&lt;/strong> it will bring you to a quiz just like before with questions in a random order that have their answers hidden.&lt;/p>
&lt;p>&lt;a href="/studybuddy/new_quiz.png">&lt;img src="/studybuddy/new_quiz.png" alt="New quiz screen of StudyBuddy">&lt;/a>&lt;/p>
&lt;h2 id="story">Story&lt;/h2>
&lt;p>The story of StudyBuddy actually began weeks before the hackathon. I was catching up with my old boss Zohaib Ahmed of Resemble AI and we were chatting about possible hackathon ideas. He was sharing with me all the cool things he imagined you could do with &lt;a href="https://openai.com/blog/openai-api/">GPT-3&lt;/a> (a super powerful language prediction model). One of his ideas was using GPT-3 to help students study by automatically generating questions from a piece of content. Thus began StudyBuddy.&lt;/p>
&lt;p>In preparation for the hackathon I did three things. First, I rallied the troops and formed our team. Second, the team and I settled on the StudyBuddy idea and spent a little bit of time brainstorming how it might work. Third, I talked with OpenAI to make sure that it was fine to use GPT-3 in a Hackathon demo — this was fine but I learned that we would need to go through an approval process to actually publish a production version of the application.&lt;/p>
&lt;p>On the day of the hackathon we got right to building. We were using Ruby on Rails which made scaffolding out a basic web app incredibly simple. I&amp;rsquo;m falling in love with Ruby on Rails and will be writing about it more in the future.&lt;/p>
&lt;p>Ryan primarily focused on the GPT-3 prompt engineering and the code that interfaced between our web app and the GPT-3 API. The rest of us focused on building out the CRUD operations for different resources, background jobs to generate questions, and styling. Kudos to Armi for crafting the lovely logo.&lt;/p>
&lt;p>Thanks to the insane speed of Rails we had something respectable looking by 7pm (6 hours in). By roughly 1am (12 hours in) we had integrated the Rails app with the GPT-3 API and it was working from start to finish. We ended up staying up until about 7am (18 hours in) polishing the UI and working out bugs in the question generation. Pleased with how far we got, we hit the sack and managed about 3 hours of sleep before waking up and adding a few polishing touches.&lt;/p>
&lt;p>After the hacking ended at 1pm the next day we spent the afternoon prepping and delivering our demo. The day finished off with closing ceremonies at 7pm where it was announced that we finished in first place!&lt;/p>
&lt;p>It&amp;rsquo;s still to be determined but our group is in talks about taking the application through the OpenAI reviewal process and publicly releasing it.&lt;/p>
&lt;h2 id="reflections">Reflections&lt;/h2>
&lt;p>I always learn lots at hackathons and this year was no exception. The following are some of my biggest takeaways.&lt;/p>
&lt;p>Picking and planning an idea in advance of a hackathon is incredibly helpful. Having a plan in advance meant that we didn&amp;rsquo;t need to waste the first few hours of the hackathon trying to think of or refine an idea. Looking at the commit history I can see that our first commit was checked in a mere 13 minutes after the start of the hackathon.&lt;/p>
&lt;p>Two things can really make a demo pop: aesthetic and wow-factor. We had a bit of both. Because building the CRUD logic was so easy with Rails we had a lot of time to nicely style the project with TailwindCSS. By using GPT-3 we were able to borrow some of it&amp;rsquo;s wow-factor and inject it into our project. If you&amp;rsquo;re looking to win a hackathon you want to find an idea that is both really cool and really easy to execute on.&lt;/p>
&lt;p>In hindsight we should have gone to bed earlier. From about 4am to 7am before we went to bed, we were effectively zombies. Bugs that probably would have been 20 minute fixes in the right state of mind, ended up being multi-hour slogs with our level of exhaustion. If we had gone to bed by 4am we could have woken up at 8am well rested and done a lot more high quality work in the remaining 5 hours.&lt;/p>
&lt;p>I don&amp;rsquo;t think I&amp;rsquo;ll ever build a web app without Rails again. I simply just cannot believe how productive it makes me. Do I miss static types and being able to fiddle with configuration? Sure. But, I&amp;rsquo;m happy to give up all of that for the insane productivity of Rails.&lt;/p>
&lt;p>Overall I&amp;rsquo;m super pleased with how everything went and that I got to win a hackathon during my university career. Who knows, maybe we&amp;rsquo;ll even publicly release StudyBuddy.&lt;/p></description></item><item><title>Year in Review: 2021</title><link>/blog/2021/year-in-review-2021/</link><pubDate>Thu, 30 Dec 2021 00:00:00 +0000</pubDate><guid>/blog/2021/year-in-review-2021/</guid><description>&lt;p>As 2021 comes to a close it&amp;rsquo;s time to look in the rear-view mirror and reflect on what I&amp;rsquo;ve done, where I&amp;rsquo;ve been, and what I&amp;rsquo;ve consumed. I&amp;rsquo;ll also look forward to what I want next year to look like.&lt;/p>
&lt;h2 id="top-10-experiences">Top 10 Experiences&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Travelling to Portugal for ~2 weeks&lt;/strong> — port tasting and the Castle of Marvão were some of the highlights.&lt;/li>
&lt;li>&lt;strong>Spending ~2 weeks with my family in Mexico&lt;/strong> — I was lucky to be able to work remotely for Resemble AI during this time. Shout-out to Karen&amp;rsquo;s for their happy hour piña coladas.&lt;/li>
&lt;li>&lt;strong>Learning to play guitar&lt;/strong> — I opted to learn guitar on a whim one year ago and it is one of the best decisions I made this year. Learning guitar has been challenging in all the right ways and playing guitar is soothing in all sorts of unanticipated ways. Leading carols for my family this christmas was the cherry on top.&lt;/li>
&lt;li>&lt;strong>Biking a metric century with my Dad and Sister&lt;/strong> — as an added bonus I got to do the century on my new gravel bike Rose.&lt;/li>
&lt;li>&lt;strong>Volunteering at Sport Central&lt;/strong> — every Tuesday night I go to Sport Central to repair bikes for kids in need. I&amp;rsquo;ve learned so much about bike repair and become close with a great group of guys.&lt;/li>
&lt;li>&lt;strong>Working at Resemble AI&lt;/strong> — I always seem to enjoy the cadence of working at a startup. Foo Fridays are amazing, I learned so much reporting directly to Zohaib, and it was a joy to get to learn Swift and iOS SDK development on the job.&lt;/li>
&lt;li>&lt;strong>Hiking up to Landslide Lake&lt;/strong> — our campsite on the second night was stunning.&lt;/li>
&lt;li>&lt;strong>Working with Armi and Cyrus&lt;/strong> — I got to work with them on a number of school projects, GrokJS, and CompE+. We see eye to eye on so much and I look forward to building more stuff with them.&lt;/li>
&lt;li>&lt;strong>Getting vaccinated&lt;/strong> — what a triumph of science and technology.&lt;/li>
&lt;li>&lt;strong>Canoeing&lt;/strong> — no overnight trips this year, but still thankful for the two times I got to go on the river.&lt;/li>
&lt;/ol>
&lt;p>Honorable mention to &lt;strong>buying a Quest 2&lt;/strong>. It&amp;rsquo;s an amazing piece of hardware and it has some really great games on it.&lt;/p>
&lt;h2 id="top-10-movies-and-tv-shows">Top 10 Movies and TV Shows&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Bo Burnham: Inside&lt;/strong> — this literally left my jaw on the floor.&lt;/li>
&lt;li>&lt;strong>Don&amp;rsquo;t Look Up&lt;/strong> — I don&amp;rsquo;t even know how to describe how perfect the social commentary in this is. I wonder if my death will involve a Brontaroc?&lt;/li>
&lt;li>&lt;strong>tick, tick&amp;hellip;BOOM!&lt;/strong> — the majority of the Broadway references and cameos flew over my head, but it was such a beautiful interweaving of stories and musicals.&lt;/li>
&lt;li>&lt;strong>Schitt&amp;rsquo;s Creek&lt;/strong> — A+ for a Canadian comedy. Like most of my other favorite TV shows the over-the-top characters make it for me.&lt;/li>
&lt;li>&lt;strong>Derry Girls&lt;/strong> — impossible to watch without captions but so funny! Orla is the best.&lt;/li>
&lt;li>&lt;strong>Only the Brave&lt;/strong> — I think I cried at the end.&lt;/li>
&lt;li>&lt;strong>The Billion Dollar Code&lt;/strong> — don&amp;rsquo;t be evil 😏.&lt;/li>
&lt;li>&lt;strong>Argo&lt;/strong> — a great movie made even better by the fact that it is based on real events.&lt;/li>
&lt;li>&lt;strong>The Pianist&lt;/strong> — this was an impactful watch.&lt;/li>
&lt;li>&lt;strong>Solar Opposites&lt;/strong> — the secondary plot about the humans in the ant farm is Rick and Morty levels of unique.&lt;/li>
&lt;/ol>
&lt;p>Honorable mentions for &lt;strong>The Office&lt;/strong>, &lt;strong>Rick and Morty&lt;/strong>, &lt;strong>Drive to Survive&lt;/strong>, &lt;strong>Brooklyn Nine-Nine&lt;/strong> and &lt;strong>Archer&lt;/strong>. These shows either released great new seasons or in the case of The Office are amazing enough to re-watch a third time.&lt;/p>
&lt;h2 id="top-10-books">Top 10 Books&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>To Sleep in a Sea of Stars&lt;/strong> — one of the most compelling science fiction books I&amp;rsquo;ve read. Paolini&amp;rsquo;s visions of ship minds and augmented reality are riveting.&lt;/li>
&lt;li>&lt;strong>The Psychology of Money&lt;/strong> — as far as I&amp;rsquo;m concerned this book is a must read. It seriously changed how I think about my risk tolerances for investing.&lt;/li>
&lt;li>&lt;strong>Cloud Atlas&lt;/strong> — this is worth reading for the unique narrative structure alone (think Russian nesting doll).&lt;/li>
&lt;li>&lt;strong>Crypto Theses for 2022&lt;/strong> — stumbled across this in an email newsletter and then spent the next two days chained to my desktop reading it like a madman. It sold me on NFTs with this quote, &amp;ldquo;If a big part of our future lives are spent living in global, virtual, interconnected worlds (the metaverse), then NFTs are some of the primary building blocks for everything in that world&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Foundation series&lt;/strong> — a classic series that comes highly recommended. Fascinating to follow the thread of the Foundation through a century spanning history.&lt;/li>
&lt;li>&lt;strong>Bird Box&lt;/strong> — a terrifying read. Way better than the movie. A story about wearing blindfolds deserves to be read, not watched 🤦.&lt;/li>
&lt;li>&lt;strong>Real-World Cryptography&lt;/strong> — of all places I read this on the beaches of Mexico while sipping piña coladas. Cryptography is still a complex and risk-laden field but this book primed me with enough of the basics that I feel prepared to navigate it anyways.&lt;/li>
&lt;li>&lt;strong>The Wealthy Barber&lt;/strong> — this was probably the fourth time I&amp;rsquo;ve read the book, but I always learn something new. It&amp;rsquo;s outdated in parts but the core message still holds water.&lt;/li>
&lt;li>&lt;strong>Operating Systems: Three Easy Pieces&lt;/strong> — this book unlocked a big part of the modern computing environment for me. I love peeling back layers of abstraction and seeing how something works underneath.&lt;/li>
&lt;li>&lt;strong>The Mom Test&lt;/strong> — an indie hackers classic. For the most part it lives up to the hype and provides you with some solid mental models for customer conversations and product discovery.&lt;/li>
&lt;/ol>
&lt;h2 id="top-10-podcasts">Top 10 Podcasts&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>All-In&lt;/strong> — billionaires shooting the shit, &amp;rsquo;nuff said.&lt;/li>
&lt;li>&lt;strong>Bankless&lt;/strong> — my crypto red pill.&lt;/li>
&lt;li>&lt;strong>Acquired&lt;/strong> — I never thought I&amp;rsquo;d spend nine hours of my life learning about Berkshire Hathaway; yet here I am.&lt;/li>
&lt;li>&lt;strong>Indie Hackers&lt;/strong> — Courtland is a masterclass podcaster and the show has only improved as he has strayed from the rigid interview format.&lt;/li>
&lt;li>&lt;strong>Lex Fridman&lt;/strong> — Lex is such a delightful anomaly. I love being a fly on the wall of his conversations.&lt;/li>
&lt;li>&lt;strong>The Art of Product Podcast&lt;/strong> — This new find fuels my passion for building.&lt;/li>
&lt;li>&lt;strong>CoRecursive&lt;/strong> — the way in which Adam wraps technical problems in a story about humans makes me love the field of technology.&lt;/li>
&lt;li>&lt;strong>Changelog Family&lt;/strong> — the information density of the average episode is a bit too low for me but there are diamonds in the rough.&lt;/li>
&lt;li>&lt;strong>My First Million&lt;/strong> — at times the hustle is a bit too much, but with timestamps you can find some diamonds in the rough.&lt;/li>
&lt;li>&lt;strong>The Tim Ferriss Show&lt;/strong> — perhaps Tim could make less liberal use of his soap box, but — you guessed it — some episodes are diamonds in the rough.&lt;/li>
&lt;/ol>
&lt;h2 id="looking-forward-to-2022">Looking Forward to 2022&lt;/h2>
&lt;h3 id="career">Career&lt;/h3>
&lt;p>By the end of this year I&amp;rsquo;ll graduate. With that in mind I&amp;rsquo;ll need to start making some decisions about where I want to work full-time and where I want to live. I also want to consider less traditional options like digital nomading or consulting for the start of 2023.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Graduate!&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Leverage my connections to have multiple full-time offers post-graduation.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Seriously research the possibility of digital nomading or consulting at the start of 2023.&lt;/li>
&lt;/ul>
&lt;h3 id="travel-and-adventure">Travel and Adventure&lt;/h3>
&lt;p>Thanks to remote schooling and work I spend a lot of time at home. Travelling abroad and doing adventurous things is a great way to spice up my life.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Travel to Eastern Canada.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do a multi-night canoe trip on the North Saskatchewan.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Bike an imperial century.&lt;/li>
&lt;/ul>
&lt;h3 id="health">Health&lt;/h3>
&lt;p>I&amp;rsquo;ve had some trouble with physical and mental health problems over 2021. Looking forward to 2022 I want to take a more holistic approach to my health, or in other words, I want to do a lot of yoga.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build a habit of doing yoga multiple times a week.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Do a mix of body weight exercises at home and weightlifting at the gym.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep biking and training for a century.&lt;/li>
&lt;/ul>
&lt;h3 id="digital-life">Digital Life&lt;/h3>
&lt;p>I find the digital world to be both a blessing and a curse, software itself is amoral but amplifies the underlying good or bad. On one hand I&amp;rsquo;ve never been more connected, empowered, and informed. On the other hand I&amp;rsquo;ve never felt so isolated, paralyzed, and confused. Not quite sure how to get off the treadmill or if I even want to, but the following seem like some rational first steps.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Avoid having my phone be the first thing I interact with in the morning.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Limit the empty calories I consume i.e. Instagram, Youtube, etc.&lt;/li>
&lt;/ul>
&lt;h3 id="finances">Finances&lt;/h3>
&lt;p>2021 was a big year for my finances: I read some great investing books; started investing in crypto; got my traditional investments in order; and built a big spreadsheet to help me keep track of my overall financial direction. I&amp;rsquo;d be happy if all I did in 2022 was continue to dollar cost average into my investments and keep my spreadsheet up to date — compounding will take care of the rest.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Keep dollar cost averaging into passive index funds, ETH, BTC.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep my spreadsheet up to date.&lt;/li>
&lt;/ul>
&lt;h3 id="building">Building&lt;/h3>
&lt;blockquote class="twitter-tweet">&lt;p lang="en" dir="ltr">Greatest meme ever &lt;a href="https://t.co/iuCLBUgFdD">pic.twitter.com/iuCLBUgFdD&lt;/a>&lt;/p>&amp;mdash; Zuhayeer Musa (@zuhayeer) &lt;a href="https://x.com/zuhayeer/status/1449079602580652035?ref_src=twsrc%5Etfw">October 15, 2021&lt;/a>&lt;/blockquote>
&lt;script async src="https://platform.x.com/widgets.js" charset="utf-8">&lt;/script>
&lt;p>Building things makes me happy. I don&amp;rsquo;t intend to be prescriptive about what I&amp;rsquo;ll build in 2022 — all that matters is that I build something.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Build and publish 2+ projects.&lt;/li>
&lt;/ul>
&lt;h3 id="reading">Reading&lt;/h3>
&lt;p>Last year I focused on reading less short/medium-form blog content and more long-form book content. Blogs have their place, but I felt that I was missing some of the nuance you find in books. I also wanted to rediscover fiction. It was a good start last year but I want to pick up the pace. The Kobo I got for christmas should hopefully assist with this.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Read 1.5+ books a month (for a total of 18+ over the year).&lt;/li>
&lt;/ul>
&lt;h3 id="writing">Writing&lt;/h3>
&lt;blockquote class="twitter-tweet">&lt;p lang="en" dir="ltr">If you’re overthinking, write.&lt;br>&lt;br>If you’re underthinking, read.&lt;/p>&amp;mdash; Alex &amp;amp; Books 📚 (@AlexAndBooks_) &lt;a href="https://x.com/AlexAndBooks_/status/1446883211393503232?ref_src=twsrc%5Etfw">October 9, 2021&lt;/a>&lt;/blockquote>
&lt;script async src="https://platform.x.com/widgets.js" charset="utf-8">&lt;/script>
&lt;p>I tend to overthink (understatement of the year) so I should definitely be writing more. In the first year of this blog I managed to write eight posts. I chalk it up to a productive spurt in the early COVID lockdowns. In 2021 I only managed to write five. I think it&amp;rsquo;d be a big win if I wrote ten or more posts in 2022. Writing has compounding benefits so I want to start the compounding sooner than later.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Publish 10+ blog posts.&lt;/li>
&lt;/ul>
&lt;h3 id="guitar">Guitar&lt;/h3>
&lt;p>Learning guitar was one of the best decisions I made in 2021 so I want to keep my foot on the gas pedal. The hardest thing here will be continuing to do focused practice; at the one year mark I have enough skill that I find it really easy to waste a whole practice session just noodling rather than working on the fundamentals.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Beginner Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Complete Justin Guitar Ear Training and Theory up to Grade 3.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Keep playing in front of family and friends to practice performing.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Find more people to jam with.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Stretch goal: Record a cover of a song.&lt;/li>
&lt;/ul>
&lt;h3 id="web-30">Web 3.0&lt;/h3>
&lt;blockquote>
&lt;p>If you’re in college, I’d encourage you to spend the spring semester (maybe redeploy your book budget into ETH gas fees), learning and testing different crypto protocols on Earn, Rabbithole and other places. Best case scenario, you hit the lotto on a few more airdrops. Worst case? You flunk out, and you’ll get hired on the spot by a crypto company once you show them your degen score and NFT merit badge library.
&lt;br>
— &lt;strong>Ryan Selkis in Crypto Theses for 2022&lt;/strong>&lt;/p>&lt;/blockquote>
&lt;p>2021 was the year that I got red-pilled by crypto; I started buying ETH and BTC; I started mining HNT; but most of all I read and listened to a lot of crypto content. In 2022 I want to double down on actually tinkering around with crypto. I&amp;rsquo;m convinced that Web 3.0 is going to be huge so I want to get in early. The end goal of the tinkering is to discover what still needs to be built and to go build it.&lt;/p>
&lt;div class="begin-task-list">&lt;/div>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Continue to dollar cost average my way into ETH and BTC.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Purchase or host more Helium miners.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Register an ENS domain.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Try out some DeFi protocols.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Buy some NFTs and get involved with the communities.&lt;/li>
&lt;/ul></description></item><item><title>Compiling espeak-ng in Xcode for iOS app</title><link>/blog/2021/compiling-espeak-ng-in-xcode-for-ios-app/</link><pubDate>Fri, 12 Nov 2021 00:00:00 +0000</pubDate><guid>/blog/2021/compiling-espeak-ng-in-xcode-for-ios-app/</guid><description>&lt;p>Recently at work I&amp;rsquo;ve been busy getting neural net text-to-speech to run offline on iOS. One of the steps for this text-to-speech pipeline is turning text into phonemes. At Resemble AI we depend on a library called eSpeak to phonemize the text. The library can be found on Github at &lt;a href="https://github.com/espeak-ng/espeak-ng">espeak-ng&lt;/a>. It turned out to be quite an involved process getting eSpeak to compile for an iOS app in Xcode so I wanted to share some tips on how I got it to work.&lt;/p>
&lt;h2 id="assumptions">Assumptions&lt;/h2>
&lt;p>This post is just an overview of the issues I ran into while trying to get eSpeak to compile. I won&amp;rsquo;t be walking you through each step and line of code you need to type. I also make a number of assumptions.&lt;/p>
&lt;ol>
&lt;li>You already have a stock app setup to work with.&lt;/li>
&lt;li>We are compiling eSpeak into a stock iOS app, not a framework.&lt;/li>
&lt;li>The app is written in Swift using Xcode 13.&lt;/li>
&lt;li>You only need to use the phonemization capabilities of eSpeak. The library can actually do a lot more, but I don&amp;rsquo;t need any of that functionality so I just commented out irrelevant code as necessary.&lt;/li>
&lt;/ol>
&lt;h2 id="add-the-code-to-the-project">Add the code to the project&lt;/h2>
&lt;p>To start let&amp;rsquo;s add the eSpeak code to the app. Clone the &lt;a href="https://github.com/espeak-ng/espeak-ng">repository&lt;/a> to your machine and then copy the code into your app&amp;rsquo;s folder with Finder. Now in Xcode right click your app&amp;rsquo;s folder (not the project) and select &lt;code>Add files to &amp;quot;MyApp&amp;quot;&lt;/code>. Go ahead and select the eSpeak folder we moved earlier. Make sure that you deselect &lt;code>Copy items if needed&lt;/code> and that you select &lt;code>Create Groups&lt;/code>.&lt;/p>
&lt;p>Note that when adding this code it probably all got copied to the bundle. Not sure why this happens. Go to the apps build phases and remove all these files from the bundle.&lt;/p>
&lt;h2 id="setup-a-module">Setup a module&lt;/h2>
&lt;p>Great! Now let&amp;rsquo;s get Xcode to recognize this code as a module it should compile and expose to your other Swift code. Add a &lt;code>module.modulemap&lt;/code> file to the eSpeak folder with the following contents:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>module EspeakNg [system] {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#c30">&amp;#34;./src/include/espeak-ng/speak_lib.h&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> header &lt;span style="color:#c30">&amp;#34;./src/include/espeak-ng/espeak_ng.h&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> export &lt;span style="color:#555">*&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now open the app&amp;rsquo;s build settings and add the following import path. Make sure to replace &lt;code>MyApp&lt;/code> with your app name.&lt;/p>
&lt;pre tabindex="0">&lt;code>${SRCROOT}/MyApp/espeak-ng/**
&lt;/code>&lt;/pre>&lt;p>Next in the settings add the following header search path:&lt;/p>
&lt;pre tabindex="0">&lt;code>${SRCROOT}/MyApp/espeak-ng/src/**
&lt;/code>&lt;/pre>&lt;h2 id="silencing-the-errors">Silencing the errors&lt;/h2>
&lt;p>Xcode will treat this code as a module now but it is going to fail to compile. Your mileage may very in this section, but here is a number of things I had to do to make this C code happily compile in Xcode.&lt;/p>
&lt;ol>
&lt;li>Comment out any references to the &lt;code>windows.h&lt;/code> header. This is an omnibus header that is only relevant for running eSpeak on a Windows machine. Since you&amp;rsquo;re compiling on a Mac, Xcode won&amp;rsquo;t even know how to find this header.&lt;/li>
&lt;li>Make sure to comment out any &lt;code>int main...&lt;/code> declarations within eSpeak. These are just going to conflict with your apps main function.&lt;/li>
&lt;li>Remove the header shim found at &lt;code>espeak-ng/src/include/compat/unistd.h&lt;/code>. I found that this shim was giving me errors and wasn&amp;rsquo;t actually providing anything I needed.&lt;/li>
&lt;li>Comment out the &lt;code>LoadWaveFile&lt;/code> function. I forgot why it was erroring but it was and I didn&amp;rsquo;t need it for phonemization.&lt;/li>
&lt;/ol>
&lt;p>By this point you shouldn&amp;rsquo;t have any more errors. It&amp;rsquo;s possible I&amp;rsquo;ve forgotten something though so don&amp;rsquo;t be afraid to dig into the errors and see if you can just get away with removing the offending code.&lt;/p>
&lt;h2 id="using-espeak">Using eSpeak&lt;/h2>
&lt;p>With all of that out of the way, it is actually pretty straight forward to use eSpeak to phonemize some text.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">import&lt;/span> &lt;span style="color:#0a8;font-weight:bold">EspeakNg&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Intialize the C library, more on what dataPath is later&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>espeak_ng_InitializePath(dataPath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">context&lt;/span>: espeak_ng_ERROR_CONTEXT? = &lt;span style="color:#069;font-weight:bold">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">result&lt;/span>: espeak_ng_STATUS = espeak_ng_Initialize(&amp;amp;context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">guard&lt;/span> result.rawValue == &lt;span style="color:#f60">0&lt;/span> &lt;span style="color:#069;font-weight:bold">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">fatalError&lt;/span>(&lt;span style="color:#c30">&amp;#34;Failed to initialize eSpeak&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>espeak_SetVoiceByName(&lt;span style="color:#c30">&amp;#34;en-us&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Phonemize some text&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">options&lt;/span>: &lt;span style="color:#366">Int32&lt;/span> = &lt;span style="color:#f60">0&lt;/span> &lt;span style="color:#555">|&lt;/span> espeakPHONEMES_IPA
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">text&lt;/span> = &lt;span style="color:#c30">&amp;#34;My text I want to phonemize&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#033">textPtr&lt;/span>: UnsafeRawPointer? = UnsafeRawPointer(text)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">phonemesPtr&lt;/span> = espeak_TextToPhonemes(&amp;amp;textPtr, espeakCHARS_UTF8, options)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">if&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">phonemes&lt;/span> = phonemesPtr {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">print&lt;/span>(&lt;span style="color:#366">String&lt;/span>(cString: phonemes))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="missing-dependencies">Missing dependencies&lt;/h2>
&lt;p>I&amp;rsquo;ve actually lied to you though — eSpeak won&amp;rsquo;t quite work yet. It turns out that it depends on the data found under &lt;code>espeak-ng-data/&lt;/code>. This data contains dictionaries and other mappings that are needed for phonemization. When eSpeak is installed as a CLI on a traditional unix system this data is copied to somewhere like &lt;code>/usr/local&lt;/code> where the CLI can read it. Where do we put this for an iOS app?&lt;/p>
&lt;p>The most analogous place to &lt;code>/usr/local&lt;/code> for an iOS app is the &lt;a href="https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html">apps library&lt;/a>. This a place in the filesystem specifically for resources an app depends on. Let&amp;rsquo;s copy the data there so that eSpeak can get at it. We will also check to make sure the data has been copied there before we try to phonemize text.&lt;/p>
&lt;p>Before we can copy the data to the library it must be in the apps bundle. In the app settings drag the &lt;code>espeak-ng-data/&lt;/code> folder to the &lt;code>Copy Bundle Resources&lt;/code> build phase.&lt;/p>
&lt;p>Now that we have the data in the app&amp;rsquo;s bundle we can copy it over to the app&amp;rsquo;s library when we run the app. Make sure to only do this if it hasn&amp;rsquo;t been already done.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-swift" data-lang="swift">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Copy espeak-ng-data from bundle to app library&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">library&lt;/span> = FileManager.&lt;span style="color:#069;font-weight:bold">default&lt;/span>.urls(&lt;span style="color:#069;font-weight:bold">for&lt;/span>: .libraryDirectory, &lt;span style="color:#069;font-weight:bold">in&lt;/span>: .userDomainMask)[&lt;span style="color:#f60">0&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">espeakNgData&lt;/span> = library.appendingPathComponent(&lt;span style="color:#c30">&amp;#34;espeak-ng-data&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">fileManager&lt;/span> = FileManager.&lt;span style="color:#069;font-weight:bold">default&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// This is the path to the data that we referenced in espeak_ng_InitializePath above&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dataPath = &lt;span style="color:#366">String&lt;/span>(espeakNgData.absoluteURL.standardizedFileURL.absoluteString.&lt;span style="color:#366">dropFirst&lt;/span>((espeakNgData.scheme?.&lt;span style="color:#366">count&lt;/span> ?? &lt;span style="color:#555">-&lt;/span>&lt;span style="color:#f60">3&lt;/span>) &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#f60">3&lt;/span>).&lt;span style="color:#366">dropLast&lt;/span>())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Only copy files if they don&amp;#39;t exist&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">if&lt;/span> &lt;span style="color:#555">!&lt;/span>fileManager.fileExists(atPath: espeakNgData.path) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">guard&lt;/span> &lt;span style="color:#069;font-weight:bold">let&lt;/span> &lt;span style="color:#033">bundle&lt;/span> = Bundle.main.path(forResource: &lt;span style="color:#c30">&amp;#34;espeak-ng-data&amp;#34;&lt;/span>, ofType: &lt;span style="color:#c30">&amp;#34;&amp;#34;&lt;/span>) &lt;span style="color:#069;font-weight:bold">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#366">fatalError&lt;/span>(&lt;span style="color:#c30">&amp;#34;Failed to find path to espeak-ng-data in app bundle&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">try&lt;/span>! fileManager.copyItem(atPath: bundle, toPath: espeakNgData.path)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="pregenerated-data">Pregenerated data&lt;/h2>
&lt;p>Turns out I&amp;rsquo;ve lied to you again. Sorry, I promise that was the last time. Remember how eSpeak depends on some data (dictionaries, mappings, etc.) to work? Well this data in &lt;code>espeak-ng-data/&lt;/code> isn&amp;rsquo;t ready out of the box when you clone the repository. It actually has to be generated by eSpeak itself. This generation process takes the relatively small amount of base data found in &lt;code>espeak-ng-data/&lt;/code> and generates the full set of data you need.&lt;/p>
&lt;p>While you could conceivably generate this data on the fly from within your iOS app using the eSpeak API&amp;rsquo;s I think this is needlessly difficult. I found it easier to pre-generate this data and then directly include that in my bundle.&lt;/p>
&lt;p>You could get the pre-generated data in one of two ways. If you have eSpeak installed on your system from a package manager you could go find the data and copy it. It will probably be living somewhere like &lt;code>/usr/local/espeak-ng-data/&lt;/code>. Alternatively, if you can&amp;rsquo;t install eSpeak through a package manager you could compile it yourself and then go find the data in the same place. This is what I did.&lt;/p>
&lt;h2 id="all-done">All done&lt;/h2>
&lt;p>And with all that you should hopefully have eSpeak running within an iOS app phonemizing text for you. To repeat myself, these are just the Coles Notes for getting this to work so it is totally possible that you&amp;rsquo;ll run into some other errors. If this was helpful to you or you got stuck along the way please reach out to me — I&amp;rsquo;d love to talk.&lt;/p></description></item><item><title>Ideas are worthless, execution is everything</title><link>/blog/2021/ideas-are-worthless-execution-is-everything/</link><pubDate>Thu, 05 Aug 2021 00:00:00 +0000</pubDate><guid>/blog/2021/ideas-are-worthless-execution-is-everything/</guid><description>&lt;p>This post serves as a reminder to myself that &amp;ldquo;&lt;em>Ideas are worthless, execution is everything&lt;/em>&amp;rdquo;&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. I want to stop believing that the only thing holding me back from starting a company/open-source project/etc. is a lack of good ideas.&lt;/p>
&lt;p>The idea fallacy is so common that the entrepreneurial community has developed memes to defend against it; &amp;ldquo;&lt;em>Ideas are worthless, execution is everything&lt;/em>&amp;rdquo;; &amp;ldquo;&lt;em>10% idea, 90% execution&lt;/em>&amp;rdquo;. However, I still struggle with this so here are 3 examples of times where:&lt;/p>
&lt;ol>
&lt;li>I had a decent idea.&lt;/li>
&lt;li>Didn&amp;rsquo;t execute on the idea.&lt;/li>
&lt;li>Later discovered that someone else executed on the idea and that I missed my shot.&lt;/li>
&lt;/ol>
&lt;p>Let these examples remind me that I need to stop wishing for the perfect idea — I just need to &lt;strong>execute&lt;/strong>.&lt;/p>
&lt;p>Maybe you&amp;rsquo;ll find it helpful too.&lt;/p>
&lt;h2 id="reverse-searching-podcast-guests">Reverse searching podcast guests&lt;/h2>
&lt;ol>
&lt;li>I listen to a lot of podcasts. Sometimes I&amp;rsquo;ll really enjoy the guest on a podcast and I&amp;rsquo;ll want to discover all of the other podcasts this guest has been on. This is really hard to do. I wanted to make a website that would let you reverse search for podcast guests to find all the podcasts they&amp;rsquo;ve been on.&lt;/li>
&lt;li>I didn&amp;rsquo;t execute.&lt;/li>
&lt;li>Months later while listening to the &lt;a href="https://runninginproduction.com/">Running in Production podcast&lt;/a> I discovered that someone made a &lt;a href="https://www.listenaddict.com/">website&lt;/a> solving this exact problem.&lt;/li>
&lt;/ol>
&lt;h2 id="remote-learning-questions-platform">Remote learning questions platform&lt;/h2>
&lt;ol>
&lt;li>Because of the pandemic I was entering into a semester of classes where everything would be delivered remotely. I wanted to build a platform that would allow students in a class to signal that they were confused. If the professor saw that enough students were confused they would be able to slow down. The platform would also support questions with some sort of upvote system.&lt;/li>
&lt;li>I didn&amp;rsquo;t execute.&lt;/li>
&lt;li>Listening to the &lt;a href="https://www.indiehackers.com/podcast/032-peter-and-calvin-of-segment">Indie Hackers Podcast&lt;/a> I learned that the founders of Segment had a built an application like this in the past. It ultimately didn&amp;rsquo;t work but that was without the remote conditions imposed by the pandemic.&lt;/li>
&lt;/ol>
&lt;h2 id="augmented-reality-clothes-fitting">Augmented reality clothes fitting&lt;/h2>
&lt;ol>
&lt;li>In an entrepreneurial course at my school I worked with a team to mock design/market/sell/etc. an AR clothes fitting mirror. The idea was that you would be able to look at yourself in a special mirror and it would project different clothing onto your body.&lt;/li>
&lt;li>I didn&amp;rsquo;t execute.&lt;/li>
&lt;li>On Hacker News I discovered that there is a &lt;a href="https://revery.ai/">company&lt;/a> actually doing this now.&lt;/li>
&lt;/ol>
&lt;h1>&lt;/h1>
&lt;p>Enough is enough Caleb. Go execute!&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>While I was doing research for this post I found a &lt;a href="https://sive.rs/multiply">more nuanced&lt;/a> take on this idea that I really like.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>How to Setup Oh my Posh On Ubuntu</title><link>/blog/2021/how-to-setup-oh-my-posh-on-ubuntu/</link><pubDate>Tue, 18 May 2021 00:00:00 +0000</pubDate><guid>/blog/2021/how-to-setup-oh-my-posh-on-ubuntu/</guid><description>&lt;p>While onboarding at my latest internship I noticed that my manager had a very aesthetically pleasing shell. Upon further inspection I discovered he was using something called &lt;a href="https://ohmyposh.dev/">Oh my Posh&lt;/a>. It markets itself as, &amp;ldquo;A prompt theme engine for any shell.&amp;rdquo; It&amp;rsquo;s more than pretty though — it will also show you Git status, error codes, Python versions, and can be configured to do &lt;a href="https://ohmyposh.dev/docs/config-segment">more&lt;/a>.&lt;/p>
&lt;p>&lt;img src="/posh_terminal.png" alt="My terminal using Oh my Posh">&lt;/p>
&lt;p>I ended up installing it on both my Windows machine for work and my personal Ubuntu machine. However, I found the process more difficult on Ubuntu so I&amp;rsquo;m writing down the steps here.&lt;/p>
&lt;div class="callout">
Note that your machine might be a bit different than mine. Read the steps carefully as you may need to deviate from them. I&amp;rsquo;m running Ubuntu 20.04 with the Gnome desktop environment and I use Bash as my shell.
&lt;/div>
&lt;h2 id="install-oh-my-posh">Install Oh my Posh&lt;/h2>
&lt;p>The &lt;a href="https://ohmyposh.dev/docs/linux#installation">docs&lt;/a> are fairly clear here so I recommend you follow along with them as they will be kept up to date. As of writing this post you need to run the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">## Install Oh my Posh&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo wget https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/posh-linux-amd64 -O /usr/local/bin/oh-my-posh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo chmod +x /usr/local/bin/oh-my-posh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">## Download the themes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir ~/.poshthemes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/themes.zip -O ~/.poshthemes/themes.zip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unzip ~/.poshthemes/themes.zip -d ~/.poshthemes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chmod u+rw ~/.poshthemes/*.json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rm ~/.poshthemes/themes.zip
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="change-your-prompt">Change your prompt&lt;/h2>
&lt;p>This is also pretty straightforward in the &lt;a href="https://ohmyposh.dev/docs/linux#replace-your-existing-prompt">docs&lt;/a>. Add the following to your &lt;code>~/.bashrc&lt;/code> and then source it. Replace &lt;code>{theme}&lt;/code> below with your preferred theme. I like the Paradox theme.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#366">eval&lt;/span> &lt;span style="color:#c30">&amp;#34;&lt;/span>&lt;span style="color:#069;font-weight:bold">$(&lt;/span>oh-my-posh --init --shell bash --config ~/.poshthemes/&lt;span style="color:#555">{&lt;/span>theme&lt;span style="color:#555">}&lt;/span>.omp.json&lt;span style="color:#069;font-weight:bold">)&lt;/span>&lt;span style="color:#c30">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="setup-the-fonts">Setup the fonts&lt;/h2>
&lt;p>This is the tricky part. Start by installing a nerd font from &lt;a href="https://www.nerdfonts.com/font-downloads">here&lt;/a>. I recommend using &lt;a href="https://github.com/ryanoasis/nerd-fonts/releases/download/v2.1.0/Meslo.zip">Meslo&lt;/a>. Now you need to install the font to your system. Based on the instructions found &lt;a href="https://gist.github.com/matthewjberger/7dd7e079f282f8138a9dc3b045ebefa0">here&lt;/a> we should do the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#366">cd&lt;/span> ~
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir .fonts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unzip ~/Downloads/Meslo.zip -d ~/.fonts/Meslo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>fc-cache -fv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With the nerd font installed on the system we need to get Gnome Terminal to use it now. The gif below shows how to setup a new default profile using the font we installed.&lt;/p>
&lt;p>&lt;img src="/posh.gif" alt="Setting up a new terminal profile to use our font">&lt;/p>
&lt;p>After all that, you should be in business. Happy coding and I hope you find the theme as beautiful as I do.&lt;/p></description></item><item><title>Building GrokJS at HackED 2021</title><link>/blog/2021/building-grokjs-at-hacked-2021/</link><pubDate>Sun, 24 Jan 2021 00:00:00 +0000</pubDate><guid>/blog/2021/building-grokjs-at-hacked-2021/</guid><description>&lt;p>This past weekend I participated in &lt;a href="https://hacked-2021.devpost.com/">HackED 2021&lt;/a>, the annual hackathon put on by the U of A Computer Engineering Club. I wanted to take some time to reflect on the experience.&lt;/p>
&lt;p>Unlike previous hackathons I have attended, I came into this hackathon with a clear picture of a problem I wanted to solve. Thankfully, when I pitched the idea to my team before the hackathon, they liked it.&lt;/p>
&lt;p>I intend to write more on this topic so I will keep the explanation of the problem brief. Syntax is often a stumbling block when working with new programming languages. Before you can understand a piece of code, you need to understand what all of the characters represent. Unfortunately, it is very difficult to google for help with unfamiliar syntax. Google will likely give you documentation on the methods and functions being used, not the unfamiliar syntax.&lt;/p>
&lt;p>&lt;img src="/googling_syntax.png" alt="Trouble Googling Syntax">&lt;/p>
&lt;p>We set out to solve this problem by building a VS Code extension called &lt;a href="https://marketplace.visualstudio.com/items?itemName=grokjs.grok-js">GrokJS&lt;/a>. The extension helps you learn JavaScript syntax directly inside your editor. Walking through an example is the easiest way to explain how it works. Imagine you encounter the following JavaScript and can&amp;rsquo;t tell what it is doing:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">let&lt;/span> myArray &lt;span style="color:#555">=&lt;/span> [...[&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#f60">2&lt;/span>, &lt;span style="color:#f60">3&lt;/span>], ...[&lt;span style="color:#f60">4&lt;/span>, &lt;span style="color:#f60">5&lt;/span>, &lt;span style="color:#f60">6&lt;/span>]];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With GrokJS installed you start by highlighting a confusing section. For example you might highlight &lt;code>...[1, 2, 3]&lt;/code> to try and understand what it does. Once you have this highlighted, the editor will show an inline hint of what it is — in this case it is a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">Spread Element&lt;/a>.&lt;/p>
&lt;p>But, just the name of the syntax element often isn&amp;rsquo;t enough to understand. For more details you can hover over what you&amp;rsquo;ve highlighted. This will show a popup box with a description of what the syntax means and a link to documentation. By allowing programmers to highlight confusing syntax and get answers directly in the editor we avoid the difficult step of trying to google for an explanation.&lt;/p>
&lt;p>&lt;img src="/grokjs_demo.gif" alt="GrokJS demo">&lt;/p>
&lt;p>When we started to build the extension it was clear that there were going to be two parts. A frontend for interacting with VS Code. And a backend to parse the code for insights.&lt;/p>
&lt;p>In addition to these, for the first few hours of the hackathon we deluded ourselves into thinking that we needed to use the &lt;a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol&lt;/a> as an intermediary between the two parts. LSP is great, but was total overkill for what we were trying to do. We were dealing with a 1 editor by 1 language problem. LSP is helpful when dealing with an N editor by N language problem.&lt;/p>
&lt;p>I wasn&amp;rsquo;t directly involved with the frontend of the plugin, but from what I understand the VS Code extension API is relatively pleasant to work with. The frontend was responsible for feeding the source code and currently highlighted text to the backend. The backend would then respond with a syntax type. In the above example a type of &lt;code>Spread Element&lt;/code> would be returned. This type was then used to index into a JSON document that mapped between syntax types and documentation. This syntax to documentation mapping was built with some hand-crafted scrapers and a lot of manual labor.&lt;/p>
&lt;p>&lt;img src="/grokjs_design.png" alt="GrokJS design">&lt;/p>
&lt;p>I spent a long time consternating over how the backend should be implemented. But, in the end it was remarkably simple. First, the source code was parsed into an abstract syntax tree with the npm package &lt;a href="https://github.com/acornjs/acorn">acorn&lt;/a>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Then I used the &lt;a href="https://github.com/acornjs/acorn/tree/master/acorn-walk">acorn-walk&lt;/a> package to find the subtree of the AST that most closely fit in the highlighted range. Finally, the type of this subtree was returned.&lt;/p>
&lt;p>We managed to get the extension almost fully working by 4:30 AM. This let us grab a few hours of sleep before regrouping to polish and publish the extension. Later that afternoon we presented to the judges. At the closing ceremonies we learned that our project had finished in the top 6.&lt;/p>
&lt;p>This was the first remote hackathon I&amp;rsquo;ve attended. Like everything remote over this past year I missed the in person interactions, but regardless I had a great time. All things considered, the organizers did a great job putting it together. I&amp;rsquo;m glad I attended and very pleased with how GrokJS turned out.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Our implementation re-parsed the AST everytime something changed. We had worried that parsing an AST multiple times a second might introduce performance issues. Turned out to not be a problem. In our tests, Acorn could parse ~5000 lines of JavaScript in under 10 ms. Call me impressed.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Year in Review: 2020</title><link>/blog/2020/year-in-review-2020/</link><pubDate>Fri, 25 Dec 2020 00:00:00 +0000</pubDate><guid>/blog/2020/year-in-review-2020/</guid><description>&lt;h2 id="top-10-experiences">Top 10 Experiences&lt;/h2>
&lt;ol>
&lt;li>Freedom camping across New Zealand.&lt;/li>
&lt;li>All the adrenaline rushes (skydiving/bridge-jumping/diving).&lt;/li>
&lt;li>Evening excursion to Taupo hot pools.&lt;/li>
&lt;li>Summiting Crown Range alone.&lt;/li>
&lt;li>Becoming friends with my Airbnb host Lydia — specifically our Easter dinner and walk around Half Moon Bay.&lt;/li>
&lt;li>Moving out.&lt;/li>
&lt;li>Overnight canoe trip on the North Saskatchewan with my Dad from Genesee to Fort Saskatchewan.&lt;/li>
&lt;li>Starting to more seriously get into biking.&lt;/li>
&lt;li>Taking a road trip to Sacramento with my Dad to visit my Uncle.&lt;/li>
&lt;li>Sibling sleepovers.&lt;/li>
&lt;/ol></description></item><item><title>Nanosleep Error: Storage Size Isn't Known</title><link>/blog/2020/nanosleep-error-storage-size-isnt-known/</link><pubDate>Sun, 11 Oct 2020 00:28:03 -0600</pubDate><guid>/blog/2020/nanosleep-error-storage-size-isnt-known/</guid><description>&lt;p>Compiling a C program that used &lt;code>nanosleep&lt;/code> turned out to be quite the thorn in my side. I&amp;rsquo;m documenting this here for anyone else facing the same issues.&lt;/p>
&lt;p>&lt;strong>TL;DR&lt;/strong> — Try compiling your program with a GNU standard e.g. &lt;code>gcc -std=gnu11 ...&lt;/code>&lt;/p>
&lt;h2 id="debugging">Debugging&lt;/h2>
&lt;p>Here&amp;rsquo;s a simplified version of what I was trying to compile.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Compiled via: gcc -std=c99 main.c
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>&lt;span style="color:#099">#include&lt;/span> &lt;span style="color:#099">&amp;lt;time.h&amp;gt;&lt;/span>&lt;span style="color:#099">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#099">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#078;font-weight:bold">int&lt;/span> &lt;span style="color:#c0f">main&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">struct&lt;/span> timespec sleep;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sleep.tv_sec &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sleep.tv_nsec &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">50000000&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#c0f">nanosleep&lt;/span>(&lt;span style="color:#555">&amp;amp;&lt;/span>sleep, &lt;span style="color:#366">NULL&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I kept getting the following error and warning.&lt;/p>
&lt;pre tabindex="0">&lt;code>main.c: In function ‘main’:
main.c:4:21: error: storage size of ‘sleep’ isn’t known
struct timespec sleep;
^~~~~
main.c:7:5: warning: implicit declaration of function ‘nanosleep’ [-Wimplicit-function-declaration]
nanosleep(&amp;amp;sleep, NULL);
^~~~~~~~~
&lt;/code>&lt;/pre>&lt;p>My initial googling only turned up the suggestion to define a POSIX source macro&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> at the start of the file. This didn&amp;rsquo;t work for me.&lt;/p>
&lt;p>Inspired by some obscure forum posts, I noticed that the program compiled when I removed the &lt;code>-std=c99&lt;/code> GCC option. Peculiar, why was that making it fail? To dig in I inspected the preprocessor output.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ gcc -std&lt;span style="color:#555">=&lt;/span>c99 -E main.c | grep timespec
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> struct timespec sleep;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ gcc -E main.c | grep timespec
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>struct timespec
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> struct timespec it_interval;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> struct timespec it_value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>extern int nanosleep &lt;span style="color:#555">(&lt;/span>const struct timespec *__requested_time,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> struct timespec *__remaining&lt;span style="color:#555">)&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A grep of the C99 standard output only turned up the instance of &lt;code>timespec&lt;/code> used in &lt;code>main.c&lt;/code>. The default standard output had a &lt;code>timespec&lt;/code> struct, a &lt;code>nanosleep&lt;/code> declaration, and even more I&amp;rsquo;ve left out. These declarations resolved both the error and warning I was encountering.&lt;/p>
&lt;h2 id="going-further">Going Further&lt;/h2>
&lt;p>A fix, hooray! But, this left me with more questions than answers. First off, what standard does GCC use by default when we don&amp;rsquo;t select one? My machine is running GCC &lt;code>v7.5.0&lt;/code> and thanks to this &lt;a href="https://stackoverflow.com/a/44057210">helpful Stackoverflow answer&lt;/a> we can see that GCC is defaulting to the GNU11 standard. So apparently &lt;code>time.h&lt;/code> from the GNU11 standard&amp;rsquo;s gives us what we need where as the &lt;code>time.h&lt;/code> from the C99 standard does not.&lt;/p>
&lt;p>This begs the question of what other standards work. I tested a few of the common options and have placed the results below.&lt;/p>
&lt;h3 id="gnu89--gnu99--gnu11">GNU89 / GNU99 / GNU11&lt;/h3>
&lt;p>No issues here. The &lt;code>nanosleep&lt;/code> function works flawlessly with all these standards.&lt;/p>
&lt;h3 id="c89--c99">C89 / C99&lt;/h3>
&lt;p>Both fail to compile with the all too familiar &amp;ldquo;storage size isn&amp;rsquo;t known error&amp;rdquo;. Neither standards include &lt;code>struct timespec&lt;/code> or &lt;code>nanosleep&lt;/code>. A fun tidbit is that the C89 standard does not trigger a warning for the implicit declaration of a function — this wasn&amp;rsquo;t part of the C language yet.&lt;/p>
&lt;h3 id="c11">C11&lt;/h3>
&lt;p>C11 is something of an anomaly. It compiles successfully but still throws the implicit declaration error. However, we can quench this error by defining the POSIX source macro&lt;sup id="fnref1:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> I mentioned earlier.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>&lt;code>#define _POSIX_C_SOURCE 199309L&lt;/code>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&amp;#160;&lt;a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Adding A List Data Type To Lox</title><link>/blog/2020/adding-a-list-data-type-to-lox/</link><pubDate>Fri, 14 Aug 2020 15:38:00 -0600</pubDate><guid>/blog/2020/adding-a-list-data-type-to-lox/</guid><description>&lt;p>Lists are an important data type for any kind of serious programming. The following is a detailed walkthrough of how to add lists to the interpreted programming language Lox. We&amp;rsquo;ll go over the entire design including things like bytecode representations and syntax grammars. You can also expect a lot of code snippets&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> showing the actual implementation details.&lt;/p>
&lt;p>Lox is a programming language from the fantastic book &lt;a href="https://craftinginterpreters.com/">Crafting Interpreters&lt;/a> by &lt;a href="https://journal.stuffwithstuff.com/">Bob Nystrom&lt;/a>. If you haven&amp;rsquo;t read the book yet, I highly recommend it. This post assumes a level of familiarity with Lox and it&amp;rsquo;s C implementation, Clox&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>. But, even if you haven’t read the book, it should still be a valuable lesson on programming language design.&lt;/p>
&lt;p>My hope is that this post will help give you the confidence to start hacking on programming languages yourself. When I finished Crafting Interpreters I certainly didn&amp;rsquo;t feel qualified to keep working on the language. I missed the comfort of Robert&amp;rsquo;s witty writing, delightful drawings, and copious code-snippets. I&amp;rsquo;m here to show you that if I can add something like lists to Lox then so can you.&lt;/p>
&lt;h2 id="overview">Overview&lt;/h2>
&lt;p>When the Clox interpreter runs it starts by lexing source code into a stream of tokens. Next, it simultaneously parses the stream of tokens and compiles it into bytecode&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. Finally, the VM executes the bytecode.&lt;/p>
&lt;p>When I&amp;rsquo;m thinking about adding a new feature to a language, I tend to work roughly in the opposite direction that the interpreter does. My process looks something like this:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Think about what the desired outcome of the feature will be and what the corresponding source code would look like. Or in other words, I outline the semantics of the feature.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Design and implement the feature&amp;rsquo;s runtime. The Clox interpreter is really just a C program. Therefore, we must represent the feature&amp;rsquo;s semantics in some performant C code.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Determine the necessary opcodes for the feature. Then in the VM, tie the decoding of these opcodes to the runtime.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Formalize the grammar of the new feature&amp;rsquo;s syntax. With that in hand, write the scanning and parsing to compile bytecode for the feature.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>I generally find working in reverse easier. When I start with something like lexing I have a hard time understanding how a couple of new tokens will help me get where I want to go. Flipping the script lets me pretend everything already works and then systematically fill it in. The one disadvantage is that starting at the end often makes testing harder until you have finished the entire feature.&lt;/p>
&lt;p>With this in mind, let&amp;rsquo;s dive in!&lt;/p>
&lt;h2 id="semantics-and-source-code">Semantics and Source Code&lt;/h2>
&lt;p>Semantics is just a fancy way of saying what we want something to do. We should start with the basics. How do we want the user of our language to define a list literal?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// In an assignment
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> foo &lt;span style="color:#555">=&lt;/span> [&lt;span style="color:#c30">&amp;#34;a&amp;#34;&lt;/span>, &lt;span style="color:#c30">&amp;#34;b&amp;#34;&lt;/span>, &lt;span style="color:#c30">&amp;#34;c&amp;#34;&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// An empty list
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> bar &lt;span style="color:#555">=&lt;/span> [];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Directly in an expression
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>print[(&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#f60">2&lt;/span>, &lt;span style="color:#f60">3&lt;/span>)]; &lt;span style="color:#09f;font-style:italic">// Expect: [1, 2, 3]
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Commonly you&amp;rsquo;ll see the items of large lists defined across multiple lines. In many languages, it is idiomatic to include a trailing comma. Let&amp;rsquo;s support that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> foo &lt;span style="color:#555">=&lt;/span> [&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#f60">2&lt;/span>, ...&lt;span style="color:#f60">25&lt;/span>];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If all we could do with lists is define them at compile time then they would be pretty useless. We want to be able to access items in the list. We&amp;rsquo;d also like to be able to store new values into the list.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> foo &lt;span style="color:#555">=&lt;/span> [&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#f60">2&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print foo[&lt;span style="color:#f60">0&lt;/span>]; &lt;span style="color:#09f;font-style:italic">// Expect: 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>foo[&lt;span style="color:#f60">0&lt;/span>] &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">2&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print foo[&lt;span style="color:#f60">0&lt;/span>]; &lt;span style="color:#09f;font-style:italic">// Expect: 2
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Our list type will be passed by reference. This means that functions will receive references to list arguments instead of copies. As a result, a function can modify a list without returning it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>fun modifyList(list) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list[&lt;span style="color:#f60">0&lt;/span>] &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> foo &lt;span style="color:#555">=&lt;/span> [&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#f60">2&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>modifyList(foo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print foo; &lt;span style="color:#09f;font-style:italic">// Expect: [0, 2]
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Up until this point, we&amp;rsquo;ve defined the semantics of something more like an array. It has a fixed-length set at compile-time, and it contains only a single type of value. To make this list a bit more &amp;ldquo;listy&amp;rdquo;, let&amp;rsquo;s start by saying it can store a mix of any type.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> foo &lt;span style="color:#555">=&lt;/span> [&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#c30">&amp;#34;b&amp;#34;&lt;/span>, &lt;span style="color:#069;font-weight:bold">false&lt;/span>];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As a convenience, we also want to be able to use expressions when building, indexing, or storing to lists.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> foo &lt;span style="color:#555">=&lt;/span> [&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#f60">1&lt;/span> &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#f60">1&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print foo[add(&lt;span style="color:#f60">0&lt;/span>, &lt;span style="color:#f60">1&lt;/span>)]; &lt;span style="color:#09f;font-style:italic">// Expect: 2
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>foo[foo[&lt;span style="color:#f60">0&lt;/span>]] &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">7&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print foo[&lt;span style="color:#f60">3&lt;/span> &lt;span style="color:#555">-&lt;/span> &lt;span style="color:#f60">2&lt;/span>]; &lt;span style="color:#09f;font-style:italic">// Expect: 7
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now let&amp;rsquo;s make the list a bit more dynamic. We should be able to append items to the end of the list. Also, there should be a way to delete items at a specific index. In the name of simplicity, I added this functionality as native functions. Some languages — Go as an example — do this too. However, most languages expose this functionality in other ways. More on that &lt;a href="#challenges">later&lt;/a>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> foo &lt;span style="color:#555">=&lt;/span> [&lt;span style="color:#f60">1&lt;/span>, &lt;span style="color:#f60">2&lt;/span>, &lt;span style="color:#f60">3&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// append returns nil because it modifies foo in place
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>print append(foo, &lt;span style="color:#f60">4&lt;/span>); &lt;span style="color:#09f;font-style:italic">// Expect: nil
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Now foo = [1, 2, 3, 4]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// delete returns nil because it modifies foo in place
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>print &lt;span style="color:#069;font-weight:bold">delete&lt;/span>(foo, &lt;span style="color:#f60">2&lt;/span>); &lt;span style="color:#09f;font-style:italic">// Expect: nil
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// Now foo = [1, 2, 4]
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Great work, users can now grow and shrink lists as they wish. This concludes the list semantics that we will be implementing in this post. Again for simplicity’s sake, a number of common list features have been left out. These include slicing &lt;code>print foo[1:8];&lt;/code>, reverse indexing &lt;code>print foo[-1];&lt;/code>, and more. For more details on some of these items you can check out the &lt;a href="#challenges">challenges&lt;/a> section&lt;/p>
&lt;h2 id="building-a-runtime">Building a Runtime&lt;/h2>
&lt;p>Now that we know what semantics lists should have, we need to update the interpreter’s runtime. This is where the rubber hits the road. The runtime is just some C code that does the work of executing our Lox source code. If the runtime for lists is slow, then you can be sure that lists will be slow in Lox too. Considering this, how should we implement the runtime for lists? Two data structures immediately come to mind: dynamic arrays and linked lists.&lt;/p>
&lt;p>Let&amp;rsquo;s examine linked lists first. This data structure consists of nodes holding both data and a pointer to the next node in the list. You may recall that a linked list was used in the implementation of Clox to link all the objects together for garbage collection. Leaving the formal proofs for another time a linked list has the following algorithmic complexities; &lt;code>O(n)&lt;/code> to access or modify an item; &lt;code>O(1)&lt;/code> to append an item to the list; &lt;code>O(n)&lt;/code> to delete an item.&lt;/p>
&lt;p>&lt;code>O(n)&lt;/code> complexity to access or modify an item presents an issue. We’d like something as common as accessing an item to be faster than that. The final nail in the coffin for linked lists is that the nodes aren&amp;rsquo;t guaranteed to exist in contiguous sections of memory. Scattered memory means terrible cache locality and as a result slow speeds.&lt;/p>
&lt;p>Maybe dynamic arrays will have more favorable characteristics. Spoiler alert, they do. Dynamic arrays have been used all across the implementation of Clox. Thankfully the implementation is simple. It&amp;rsquo;s nothing more than a standard array with some additional metadata tracking when the array should grow. To grow the array simply allocate a larger block of memory and copy the values over. For algorithmic complexities we get; &lt;code>O(1)&lt;/code> to access or modify an item; &lt;code>O(1)&lt;/code> (amortized time) to append an item to the end of the list; &lt;code>O(n)&lt;/code> to delete an item. Finally, because an array is a section of contiguous memory we see much better cache locality. All things considered, a dynamic array is a better fit for our list implementation.&lt;/p>
&lt;p>To make the dynamic array cooperate with the rest of Clox we will define a new &lt;code>ObjList&lt;/code> struct that extends the base &lt;code>obj&lt;/code> struct in &lt;code>object.h&lt;/code>. Remember that in the Clox implementation an object is a subtype of a Clox value. Other examples of objects include strings, functions, etc.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">typedef&lt;/span> &lt;span style="color:#069;font-weight:bold">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Obj obj;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">int&lt;/span> count;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">int&lt;/span> capacity;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value&lt;span style="color:#555">*&lt;/span> items;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} ObjList;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now let&amp;rsquo;s add the associated functionality of lists to &lt;code>object.c&lt;/code>. We need functions for retrieving, storing, appending, and deleting items in the list, a function to build a new list for us, and a helper function to verify indices. It&amp;rsquo;s a lot of code, but keep in mind we&amp;rsquo;re really just writing a wrapper around a dynamic array.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>ObjList&lt;span style="color:#555">*&lt;/span> &lt;span style="color:#c0f">newList&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> ALLOCATE_OBJ(ObjList, OBJ_LIST);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#366">NULL&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>count &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>capacity &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> list;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#078;font-weight:bold">void&lt;/span> &lt;span style="color:#c0f">appendToList&lt;/span>(ObjList&lt;span style="color:#555">*&lt;/span> list, Value value) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Grow the array if necessary
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (list&lt;span style="color:#555">-&amp;gt;&lt;/span>capacity &lt;span style="color:#555">&amp;lt;&lt;/span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>count &lt;span style="color:#555">+&lt;/span> &lt;span style="color:#f60">1&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">int&lt;/span> oldCapacity &lt;span style="color:#555">=&lt;/span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>capacity;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>capacity &lt;span style="color:#555">=&lt;/span> GROW_CAPACITY(oldCapacity);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items &lt;span style="color:#555">=&lt;/span> GROW_ARRAY(list&lt;span style="color:#555">-&amp;gt;&lt;/span>items, Value, oldCapacity, list&lt;span style="color:#555">-&amp;gt;&lt;/span>capacity);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items[list&lt;span style="color:#555">-&amp;gt;&lt;/span>count] &lt;span style="color:#555">=&lt;/span> value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>count&lt;span style="color:#555">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#078;font-weight:bold">void&lt;/span> &lt;span style="color:#c0f">storeToList&lt;/span>(ObjList&lt;span style="color:#555">*&lt;/span> list, &lt;span style="color:#078;font-weight:bold">int&lt;/span> index, Value value) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items[index] &lt;span style="color:#555">=&lt;/span> value;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Value &lt;span style="color:#c0f">indexFromList&lt;/span>(ObjList&lt;span style="color:#555">*&lt;/span> list, &lt;span style="color:#078;font-weight:bold">int&lt;/span> index) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items[index];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#078;font-weight:bold">void&lt;/span> &lt;span style="color:#c0f">deleteFromList&lt;/span>(ObjList&lt;span style="color:#555">*&lt;/span> list, &lt;span style="color:#078;font-weight:bold">int&lt;/span> index) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> (&lt;span style="color:#078;font-weight:bold">int&lt;/span> i &lt;span style="color:#555">=&lt;/span> index; i &lt;span style="color:#555">&amp;lt;&lt;/span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>count &lt;span style="color:#555">-&lt;/span> &lt;span style="color:#f60">1&lt;/span>; i&lt;span style="color:#555">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items[i] &lt;span style="color:#555">=&lt;/span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items[i&lt;span style="color:#555">+&lt;/span>&lt;span style="color:#f60">1&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>items[list&lt;span style="color:#555">-&amp;gt;&lt;/span>count &lt;span style="color:#555">-&lt;/span> &lt;span style="color:#f60">1&lt;/span>] &lt;span style="color:#555">=&lt;/span> NIL_VAL;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>count&lt;span style="color:#555">--&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#078;font-weight:bold">bool&lt;/span> &lt;span style="color:#c0f">isValidListIndex&lt;/span>(ObjList&lt;span style="color:#555">*&lt;/span> list, &lt;span style="color:#078;font-weight:bold">int&lt;/span> index) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (index &lt;span style="color:#555">&amp;lt;&lt;/span> &lt;span style="color:#f60">0&lt;/span> &lt;span style="color:#555">||&lt;/span> index &lt;span style="color:#555">&amp;gt;&lt;/span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>count &lt;span style="color:#555">-&lt;/span> &lt;span style="color:#f60">1&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#366">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#366">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Still with me? Fantastic, because we just implemented the entire runtime for lists. Not too shabby. Note that if you are following along with your own implementation there are still a few things you&amp;rsquo;ll need to add. Notably, type checking macros like &lt;code>IS_LIST&lt;/code>/&lt;code>AS_LIST&lt;/code>, function declarations in &lt;code>object.h&lt;/code>, and code to print our new list object.&lt;/p>
&lt;h2 id="operation-opcode">Operation Opcode&lt;/h2>
&lt;p>Clox is a bytecode interpreter. This means that it compiles source code into a stream of bytecode which the VM can then execute via the runtime. Bytecode consists of some opcodes (1-byte instructions) with optional operands (1-byte arguments). To implement lists we will need to add some new opcodes to Clox.&lt;/p>
&lt;p>The key to designing opcodes for a new feature is to K.I.S.S. — Keep It Simple Stupid. That is to say, if the language’s current set of opcodes can already support the new semantics you want, then do not add more opcodes. If you aren&amp;rsquo;t that lucky, then try to find the minimal number of opcodes you can add to support the new semantics. Minimizing the number of different opcodes is a tradeoff. It simplifies the VM implementation and helps you keep the common case fast. However, this comes at the cost of more complicated parsing and more time spent decoding bytecode. All the new semantics we are adding can be achieved by three new opcodes: &lt;code>OP_BUILD_LIST&lt;/code>, &lt;code>OP_INDEX_SUBSCR&lt;/code>, and &lt;code>OP_STORE_SUBSCR&lt;/code>.&lt;/p>
&lt;p>Many parts of this design and implementation are heavily influenced by Python. This includes both the semantics and syntax. The opcodes are no exception and mirror the opcodes Python uses for its list type.&lt;/p>
&lt;p>&lt;code>OP_BUILD_LIST&lt;/code> does the obvious thing. Notably, the opcode takes an operand &lt;code>itemCount&lt;/code>. This is the number of values on the stack that it should build into the list. To ensure the proper order of the list, we peek at the items on the stack in reverse order&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>. Then we pop them all off the stack. Finally, it pushes the new list onto the stack. Here is the switch-case for it in &lt;code>vm.c&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#99f">OP_BUILD_LIST&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Stack before: [item1, item2, ..., itemN] and after: [list]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> newList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">uint8_t&lt;/span> itemCount &lt;span style="color:#555">=&lt;/span> READ_BYTE();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Add items to list
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> push(OBJ_VAL(list)); &lt;span style="color:#09f;font-style:italic">// So list isn&amp;#39;t sweeped by GC in appendToList
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> (&lt;span style="color:#078;font-weight:bold">int&lt;/span> i &lt;span style="color:#555">=&lt;/span> itemCount; i &lt;span style="color:#555">&amp;gt;&lt;/span> &lt;span style="color:#f60">0&lt;/span>; i&lt;span style="color:#555">--&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> appendToList(list, peek(i));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Pop items from stack
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> &lt;span style="color:#069;font-weight:bold">while&lt;/span> (itemCount&lt;span style="color:#555">--&lt;/span> &lt;span style="color:#555">&amp;gt;&lt;/span> &lt;span style="color:#f60">0&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> push(OBJ_VAL(list));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>OP_INDEX_SUBSCR&lt;/code> stands for &lt;em>index subscript&lt;/em> — meaning access an item at a particular index. It&amp;rsquo;s corresponding code in &lt;code>vm.h&lt;/code> looks complex, but this is only because of a plethora of error handling. All it actually does is pop a list and index off of the stack, run &lt;code>indexFromList&lt;/code> and push the resulting value back on the stack.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#99f">OP_INDEX_SUBSCR&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Stack before: [list, index] and after: [index(list, index)]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> Value index &lt;span style="color:#555">=&lt;/span> pop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value list &lt;span style="color:#555">=&lt;/span> pop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>IS_LIST(list)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> runtimeError(&lt;span style="color:#c30">&amp;#34;Invalid type to index into.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> INTERPRET_RUNTIME_ERROR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> AS_LIST(list);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>IS_NUMBER(index)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> runtimeError(&lt;span style="color:#c30">&amp;#34;List index is not a number.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> INTERPRET_RUNTIME_ERROR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">int&lt;/span> index &lt;span style="color:#555">=&lt;/span> AS_NUMBER(index);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>isValidListIndex(list, index)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> runtimeError(&lt;span style="color:#c30">&amp;#34;List index out of range.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> INTERPRET_RUNTIME_ERROR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#555">=&lt;/span> indexFromList(list, AS_NUMBER(index));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> push(result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>OP_STORE_SUBSCR&lt;/code> of course means &lt;em>store subscript&lt;/em>. Instead of pulling a value from the list, we are storing a value to the list. Again, error handling makes a simple task look complicated. First pop a list, index, and item from the stack. Then store the item in the list at the particular index.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#99f">OP_STORE_SUBSCR&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Stack before: [list, index, item] and after: [item]
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> Value item &lt;span style="color:#555">=&lt;/span> pop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value index &lt;span style="color:#555">=&lt;/span> pop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value list &lt;span style="color:#555">=&lt;/span> pop();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>IS_LIST(list)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> runtimeError(&lt;span style="color:#c30">&amp;#34;Cannot store value in a non-list.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> INTERPRET_RUNTIME_ERROR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> AS_LIST(list);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>IS_NUMBER(index)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> runtimeError(&lt;span style="color:#c30">&amp;#34;List index is not a number.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> INTERPRET_RUNTIME_ERROR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">int&lt;/span> index &lt;span style="color:#555">=&lt;/span> AS_NUMBER(index);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>isValidListIndex(list, index)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> runtimeError(&lt;span style="color:#c30">&amp;#34;Invalid list index.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> INTERPRET_RUNTIME_ERROR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> storeToList(list, index, item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> push(item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And that concludes designing and implementing the new opcodes required for lists in Lox. For a full implementation be sure to update &lt;code>debug.c&lt;/code> with switch-cases for the new opcodes.&lt;/p>
&lt;h2 id="the-power-of-parsing">The Power of Parsing&lt;/h2>
&lt;p>Up until this point, our interpreter still can&amp;rsquo;t handle lists end to end. Hypothetically, the interpreter could execute hand compiled bytecode. But, hand compiling is no fun, so let&amp;rsquo;s automate it.&lt;/p>
&lt;p>First, we need to get more formal about our syntax&amp;rsquo;s &lt;a href="https://craftinginterpreters.com/appendix-i.html">grammar&lt;/a>. I&amp;rsquo;ve shown an excerpt of the grammar below that includes the modifications we will be making.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-plain" data-lang="plain">&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>assignment → ( call &amp;#34;.&amp;#34; )? IDENTIFIER ( &amp;#34;[&amp;#34; logic_or &amp;#34;]&amp;#34; )* &amp;#34;=&amp;#34; assignment
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | logic_or ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>call → subscript ( &amp;#34;(&amp;#34; arguments? &amp;#34;)&amp;#34; | &amp;#34;.&amp;#34; IDENTIFIER )* ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>subscript → primary ( &amp;#34;[&amp;#34; logic_or &amp;#34;]&amp;#34; )* ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>primary → &amp;#34;true&amp;#34; | &amp;#34;false&amp;#34; | &amp;#34;nil&amp;#34; | &amp;#34;this&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | NUMBER | STRING | IDENTIFIER | &amp;#34;(&amp;#34; expression &amp;#34;)&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> | &amp;#34;super&amp;#34; &amp;#34;.&amp;#34; IDENTIFIER | &amp;#34;[&amp;#34; list_display? &amp;#34;]&amp;#34; ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>list_display → logic_or ( &amp;#34;,&amp;#34; logic_or )* ( &amp;#34;,&amp;#34; )? ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The biggest change is that we have added a list literal to the &lt;code>primary&lt;/code> rule. It uses the utility rule &lt;code>list_display&lt;/code> which accepts multiple comma-separated items. The items are any valid expression except for an assignment as indicated by the rule &lt;code>logic_or&lt;/code>. &lt;code>list_display&lt;/code> also supports an optional trailing comma as we wanted with the snippet &lt;code>( &amp;quot;,&amp;quot; )?&lt;/code>.&lt;/p>
&lt;p>To support indexing from lists, we&amp;rsquo;ve added a new rule &lt;code>subscript&lt;/code>. It has a higher precedence than &lt;code>call&lt;/code>. This allows things like indexing into a list for a function and then calling it e.g. &lt;code>myFunctions[0]()&lt;/code>. For storing to lists we updated the &lt;code>assignment&lt;/code> rule. Both when accessing and storing items in a list, a valid index is any expression except an assignment again indicated by the rule &lt;code>logic_or&lt;/code>.&lt;/p>
&lt;p>With a formal grammar in hand, we can tie this new feature into &lt;code>compiler.c&lt;/code>. First, we add two new rules to the Pratt parsing table. When we encounter a &lt;code>[&lt;/code> in a prefix scenario we parse a list literal. Seeing an infix &lt;code>[&lt;/code> kicks off parsing of a subscript (index or store).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// {Prefix, Infix, Precedence}
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>{ list, subscript, PREC_SUBSCRIPT }, &lt;span style="color:#09f;font-style:italic">// TOKEN_LEFT_BRACKET
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>{ &lt;span style="color:#366">NULL&lt;/span>, &lt;span style="color:#366">NULL&lt;/span>, PREC_NONE }, &lt;span style="color:#09f;font-style:italic">// TOKEN_RIGHT_BRACKET
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Below is the code to parse and compile a list literal. The left bracket is already consumed by the time it enters the &lt;code>list&lt;/code> rule. If it doesn&amp;rsquo;t immediately find a right bracket it starts parsing comma-separated items one at a time. There is also an if statement to ensure it doesn&amp;rsquo;t try to parse an item after a trailing comma. Finally, it emits an &lt;code>OP_BUILD_LIST&lt;/code> and the &lt;code>itemCount&lt;/code> operand.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">static&lt;/span> &lt;span style="color:#078;font-weight:bold">void&lt;/span> &lt;span style="color:#c0f">list&lt;/span>(&lt;span style="color:#078;font-weight:bold">bool&lt;/span> canAssign) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">int&lt;/span> itemCount &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>check(TOKEN_RIGHT_BRACKET)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">do&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (check(TOKEN_RIGHT_BRACKET)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Trailing comma case
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> &lt;span style="color:#069;font-weight:bold">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parsePrecedence(PREC_OR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (itemCount &lt;span style="color:#555">==&lt;/span> UINT8_COUNT) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> error(&lt;span style="color:#c30">&amp;#34;Cannot have more than 256 items in a list literal.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> itemCount&lt;span style="color:#555">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#069;font-weight:bold">while&lt;/span> (match(TOKEN_COMMA));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> consume(TOKEN_RIGHT_BRACKET, &lt;span style="color:#c30">&amp;#34;Expect &amp;#39;]&amp;#39; after list literal.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> emitByte(OP_BUILD_LIST);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> emitByte(itemCount);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The code is simpler for the &lt;code>subscript&lt;/code> rule. First, it parses the index via &lt;code>parsePrecedence(PREC_OR)&lt;/code>. Next, it emits an &lt;code>OP_STORE_SUBSCR&lt;/code> if it sees an equal sign. Otherwise, it emits &lt;code>OP_INDEX_SUBSCR&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">static&lt;/span> &lt;span style="color:#078;font-weight:bold">void&lt;/span> &lt;span style="color:#c0f">subscript&lt;/span>(&lt;span style="color:#078;font-weight:bold">bool&lt;/span> canAssign) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parsePrecedence(PREC_OR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> consume(TOKEN_RIGHT_BRACKET, &lt;span style="color:#c30">&amp;#34;Expect &amp;#39;]&amp;#39; after index.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (canAssign &lt;span style="color:#555">&amp;amp;&amp;amp;&lt;/span> match(TOKEN_EQUAL)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> expression();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> emitByte(OP_STORE_SUBSCR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#069;font-weight:bold">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> emitByte(OP_INDEX_SUBSCR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Naturally, the parser expects that the source code has already been tokenized by the scanner. To make lists work we need to handle the cases for two new tokens. Thanks to the good design of Clox adding this is trivial.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#c30">&amp;#39;[&amp;#39;&lt;/span>&lt;span style="color:#555">:&lt;/span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> makeToken(TOKEN_LEFT_BRACKET);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#c30">&amp;#39;]&amp;#39;&lt;/span>&lt;span style="color:#555">:&lt;/span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> makeToken(TOKEN_RIGHT_BRACKET);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="finishing-up">Finishing Up&lt;/h2>
&lt;p>Alright, let&amp;rsquo;s come up for air. That was a lot of code. We should be proud of ourselves though, we finally have lists working end to end. Unfortunately, we are still missing a few key parts of the implementation before we can call it done. We need to do two things: implement the &lt;code>append&lt;/code> and &lt;code>delete&lt;/code> functions, and update the garbage collector.&lt;/p>
&lt;h3 id="append-and-delete">Append and Delete&lt;/h3>
&lt;p>&lt;code>append&lt;/code> and &lt;code>delete&lt;/code> will be available to the user through the native function interface. Both functions are simple wrappers around the runtime we built earlier in the post. The Clox native function interface does not currently handle errors so this has been left as an exercise to the reader. Here is the code in &lt;code>vm.c&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">static&lt;/span> Value &lt;span style="color:#c0f">appendNative&lt;/span>(&lt;span style="color:#078;font-weight:bold">int&lt;/span> argCount, Value&lt;span style="color:#555">*&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Append a value to the end of a list increasing the list&amp;#39;s length by 1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (argCount &lt;span style="color:#555">!=&lt;/span> &lt;span style="color:#f60">2&lt;/span> &lt;span style="color:#555">||&lt;/span> &lt;span style="color:#555">!&lt;/span>IS_LIST(args[&lt;span style="color:#f60">0&lt;/span>])) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Handle error
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> AS_LIST(args[&lt;span style="color:#f60">0&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Value item &lt;span style="color:#555">=&lt;/span> args[&lt;span style="color:#f60">1&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> appendToList(list, item);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> NIL_VAL;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">static&lt;/span> Value &lt;span style="color:#c0f">deleteNative&lt;/span>(&lt;span style="color:#078;font-weight:bold">int&lt;/span> argCount, Value&lt;span style="color:#555">*&lt;/span> args) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Delete an item from a list at the given index.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (argCount &lt;span style="color:#555">!=&lt;/span> &lt;span style="color:#f60">2&lt;/span> &lt;span style="color:#555">||&lt;/span> &lt;span style="color:#555">!&lt;/span>IS_LIST(args[&lt;span style="color:#f60">0&lt;/span>]) &lt;span style="color:#555">||&lt;/span> &lt;span style="color:#555">!&lt;/span>IS_NUMBER(args[&lt;span style="color:#f60">1&lt;/span>])) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Handle error
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> AS_LIST(args[&lt;span style="color:#f60">0&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#078;font-weight:bold">int&lt;/span> index &lt;span style="color:#555">=&lt;/span> AS_NUMBER(args[&lt;span style="color:#f60">1&lt;/span>]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> (&lt;span style="color:#555">!&lt;/span>isValidListIndex(list, index)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Handle error
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> deleteFromList(list, index);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> NIL_VAL;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="garbage-collection">Garbage Collection&lt;/h3>
&lt;p>Ensuring that garbage collection was working correctly was the most difficult part of the implementation. On its face, it is a relatively simple task. To wire up lists to the garbage collector we need to handle two switch-cases in &lt;code>memory.c&lt;/code>. In &lt;code>blackenObject&lt;/code> we mark every item in the list.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#99f">OBJ_LIST&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> (ObjList&lt;span style="color:#555">*&lt;/span>)object;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">for&lt;/span> (&lt;span style="color:#078;font-weight:bold">int&lt;/span> i &lt;span style="color:#555">=&lt;/span> &lt;span style="color:#f60">0&lt;/span>; i &lt;span style="color:#555">&amp;lt;&lt;/span> list&lt;span style="color:#555">-&amp;gt;&lt;/span>count; i&lt;span style="color:#555">++&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> markValue(list&lt;span style="color:#555">-&amp;gt;&lt;/span>items[i]);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In &lt;code>freeObject&lt;/code> we want to free both the dynamic array and the object itself.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">case&lt;/span> &lt;span style="color:#99f">OBJ_LIST&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ObjList&lt;span style="color:#555">*&lt;/span> list &lt;span style="color:#555">=&lt;/span> (ObjList&lt;span style="color:#555">*&lt;/span>)object;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FREE_ARRAY(Value&lt;span style="color:#555">*&lt;/span>, list&lt;span style="color:#555">-&amp;gt;&lt;/span>items, list&lt;span style="color:#555">-&amp;gt;&lt;/span>count);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FREE(ObjList, object);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">break&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With garbage collection, the devil is in the details. After lots of testing and careful inspection of the code, I found two bugs in my implementation. The fixes have already been included in the code of this post but I&amp;rsquo;ll outline the bugs for your benefit.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>In the &lt;code>OBJ_LIST&lt;/code> switch-case of &lt;code>freeObject&lt;/code>, I was only freeing the dynamic array. By forgetting to free the object itself too I had introduced a memory leak.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>You may have noticed the peculiar &lt;code>pop&lt;/code> and &lt;code>push&lt;/code> statements in the code for &lt;code>OP_BUILD_LIST&lt;/code>. Those are there because &lt;code>appendList&lt;/code> allocates memory which may kick off a garbage collection cycle. Since &lt;code>list&lt;/code> isn&amp;rsquo;t rooted anywhere it would get swept away. Pushing it onto the stack prevents that from happening.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c++" data-lang="c++">&lt;span style="display:flex;">&lt;span>push(OBJ_VAL(list)); &lt;span style="color:#09f;font-style:italic">// So list isn&amp;#39;t sweeped by GC in appendToList
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">&lt;/span>&lt;span style="color:#069;font-weight:bold">for&lt;/span> (&lt;span style="color:#078;font-weight:bold">int&lt;/span> i &lt;span style="color:#555">=&lt;/span> itemCount; i &lt;span style="color:#555">&amp;gt;&lt;/span> &lt;span style="color:#f60">0&lt;/span>; i&lt;span style="color:#555">--&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> appendToList(list, peek(i));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pop();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="all-done">All Done&lt;/h3>
&lt;p>Congratulations! We&amp;rsquo;ve finished a complete implementation of a performant list type. This is no small feat. Lists will certainly make programming in Lox much nicer. I hope that working through this encourages you to go and hack on programming languages too. Thanks for reading!&lt;/p>
&lt;p>If you are looking for more things to explore beyond what we&amp;rsquo;ve gone over in this post then you’re in luck. Following in the footsteps of the book Crafting Interpreters I&amp;rsquo;ve included some challenges below.&lt;/p>
&lt;div class="callout">
&lt;h3 id="challenges">Challenges&lt;/h3>
&lt;ol>
&lt;li>
&lt;p>More fully-featured languages often present a wider variety of ways to access items in a list. This includes negative indexing and slicing. Negative indexing is quite simple; an index of &lt;code>-1&lt;/code> accesses the last item, &lt;code>-2&lt;/code> the second last, and so forth. Slicing allows a user to easily extract and operate on portions of a list. In Python, something like &lt;code>myList[2:8:2]&lt;/code> would take every second item of the list starting at index &lt;code>2&lt;/code> and going to index &lt;code>8&lt;/code> exclusive. Try supporting negative indexing in &lt;code>OP_INDEX_SUBSCR&lt;/code>. Then try adding a native function with the signature &lt;code>Value slice(start, stop, step)&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Making &lt;code>append&lt;/code> and &lt;code>delete&lt;/code> native functions kept the implementation simple, but is uncommon in other languages. Two other options exist. Append and delete could be keywords that form a statement e.g. &lt;code>delete foo[0]&lt;/code>. This statement would then be compiled down into some new opcodes. Alternatively, append and delete could be made into methods on a list object. This would require some rewiring but would perhaps be more idiomatic. Pick the approach you prefer and try implementing it.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Most languages have a unified theory on iterable types. This includes how you work with lists/arrays, iterators, strings, generators, and more. Currently, our implementation is pretty lacking in this area. Research how other languages implement iterators and try adding them to Clox. Additionally, try adding the ability to index individual characters of a Clox string.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The current implementation of &lt;code>delete&lt;/code> only removes an item and reshuffles the content in the array as necessary. If many items were deleted from a list then the list would be overallocated for memory. Try adding some logic to &lt;code>delete&lt;/code> that deallocates memory when the number of items in the list becomes small enough.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div>
&lt;div id="comments">
&lt;div id="none">
&lt;p>
Leave a comment on
&lt;a class="post" href='https://www.reddit.com/r/ProgrammingLanguages/comments/ibk3xe/adding_a_list_data_type_to_lox/'>Reddit&lt;/a> / &lt;a class="post" href='https://news.ycombinator.com/item?id=24190366'>Hacker News&lt;/a>
&lt;/p>
&lt;/div>
&lt;div id="some" class="hidden">
&lt;h2 id="comment_count">Comments (1)&lt;/h2>
&lt;p>
View comments on
&lt;a href='https://www.reddit.com/r/ProgrammingLanguages/comments/ibk3xe/adding_a_list_data_type_to_lox/'>Reddit&lt;/a> / &lt;a href='https://news.ycombinator.com/item?id=24190366'>Hacker News&lt;/a>
&lt;/p>
&lt;/div>
&lt;/div>
&lt;script src="/comments.js">&lt;/script>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Lots of code will be shown but it will not be comprehensive. Not every line of code required for a complete implementation will be provided.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Lox is a dynamic programming language. In Crafting Interpreters you build two interpreters for the language. Jlox a tree-walk interpreter with Java and Clox a bytecode interpreter with C. This post modifies the Clox interpreter. I originally developed the code for adding lists to Lox on my own Lox derivative called &lt;a href="https://github.com/calebschoepp/nqq">NQQ&lt;/a>.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>In the Clox implementation parsing and compilation are squeezed into a single step. Despite the fact that they are happening at the same time, they are still providing different functions. Parsing is all about turning a flat stream of tokens into a hierarchical structure that represents the intent of the computation. Compilation is about turning that structure into something that is easier and quicker to execute. By squishing these two steps into one we are skipping building the AST. This has the possible benefits of being conceptually simpler and faster but inhibits static analysis of the code.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>When a list is parsed the items are pushed onto the stack one at a time from left to right. If &lt;code>OP_BUILD_LIST&lt;/code> were to read the items in from the top of the stack to the bottom it would be building the list in reverse.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Spenser Skates on the Indie Hackers Podcast</title><link>/blog/2020/spenser-skates-on-the-indie-hackers-podcast/</link><pubDate>Thu, 25 Jun 2020 20:40:02 -0600</pubDate><guid>/blog/2020/spenser-skates-on-the-indie-hackers-podcast/</guid><description>&lt;p>I just listened to a great Indie Hackers podcast. In it, Courtland Allen interviews Spenser Skates from &lt;a href="https://amplitude.com">Amplitude&lt;/a>. Some of the things they talked about are too good to forget; so instead of relying on my leaky brain to remember them, I&amp;rsquo;m going to pen some of them down here. All of the following quotes are from Spenser Skates. Seriously though, if you&amp;rsquo;ve got an hour (or 45 minutes if you&amp;rsquo;re like me and listen on 1.5x speed) you should go listen to the &lt;a href="https://www.indiehackers.com/podcast/040-spenser-skates-of-amplitude">podcast&lt;/a> yourself.&lt;/p>
&lt;h2 id="build-it-sell-it-build-it-sell-it">Build It, Sell It, Build It, Sell It&amp;hellip;&lt;/h2>
&lt;blockquote>
&lt;p>So the thing about great engineers is that, if you&amp;rsquo;re an engineer your natural instinct is to solve a problem, so if someone says, &amp;ldquo;Hey, I have a problem doing this.&amp;rdquo;, you&amp;rsquo;re always like, hey I can fix that for you, let me build something that can help solve that. That part is easy and all you have to do as an engineer to add onto that is like, go talk to customers.&lt;/p>&lt;/blockquote>
&lt;p>I definitely agree with this. Engineering at its core is utilizing a technical skillset to solve problems. I get lots of practice doing this in my Engineering studies and internships. The important part here is the reminder to go talk to customers. This is the next step that sets the building companies apart from solving problems. Spenser Skates continues to elaborate on this saying:&lt;/p>
&lt;blockquote>
&lt;p>If you keep doing those two core things really well, if you understand what people&amp;rsquo;s problems are and then build something to solve them, and you do both of those things really well, then, yeah that is the core motion of success for any software business. Anything else that people think is starting a company doesn&amp;rsquo;t really matter. You don&amp;rsquo;t really need to market yourself, you don&amp;rsquo;t need to have a great product strategy, or the perfect messaging, or hey I need to sort out all these things on my finances, it&amp;rsquo;s like no no no, all you have to do is build the product, sell it&amp;hellip; build it, sell it, build it, sell it, that&amp;rsquo;s it. When you&amp;rsquo;re very very early on that&amp;rsquo;s the core motion that will lead to building a successful company.&lt;/p>&lt;/blockquote>
&lt;p>This is a comfort to hear. The core motion here of &amp;ldquo;build it, sell it&amp;rdquo; is non-trivial. But, when you reduce a complex problem like building a software company down to a set of simple steps, it brings it within the realm of possibility. At that point all that&amp;rsquo;s left to do is to execute — easier said than done I suppose.&lt;/p>
&lt;p>The other cool thing about this &amp;ldquo;build it, sell it&amp;rdquo; core motion is that it seems like it would have a fly-wheel effect. As you build something great, it becomes easier to sell it. As you sell to people, it becomes clearer what you should build.&lt;/p>
&lt;h2 id="a-problem-from-the-heavens">A problem from the heavens&lt;/h2>
&lt;blockquote>
&lt;p>I think the false expectation that a lot of people have is that they&amp;rsquo;ll be handed the ideal problem down from the heavens, and the perfect definition, and know exactly what it is to build perfectly for all things.&lt;/p>&lt;/blockquote>
&lt;p>Love this!&lt;/p>
&lt;blockquote>
&lt;p>The really important thing to understand, the difference between when you&amp;rsquo;re at another job, or when you&amp;rsquo;re at school, working on things is that in the world of startups and business, the problems are not well defined, so defining the right problem is a job and super important in itself.&lt;/p>&lt;/blockquote>
&lt;p>My dad always makes the comment that University is a unique time because your life comes in discrete 4-month chunks. Once you graduate this is no longer true. This is another way of saying that the problems are well defined in school. Pay attention in class; hand in your assignments; do well on the final; you pass! What an alluring trap. It seems to me that the opportunities to do great things are not clear cut. They are ambiguous. Confusing. Scary. Or in other words not well defined.&lt;/p>
&lt;p>So how do I learn to deal with not well-defined problems? An Engineering degree isn&amp;rsquo;t giving me that experience. My internships are a bit better in this regard, but they still fall short. How can I seek out more experience like this?&lt;/p>
&lt;h2 id="productengineering">Product/Engineering&lt;/h2>
&lt;blockquote>
&lt;p>We have this saying here at Amplitude, which is that product&amp;rsquo;s job is not actually to come up with a solution. Product&amp;rsquo;s job is to define the right problem.&lt;/p>&lt;/blockquote>
&lt;p>This is a helpful framework to understand a technology company. I&amp;rsquo;ve certainly spent a lot of time at my current internship working to understand the relationship between Engineering and Product. A good interface between the two is critical for a healthy and functioning technology company. I&amp;rsquo;d propose that:&lt;/p>
&lt;p>&lt;em>Product should define the right problems to work on. Engineering should solve the problems.&lt;/em>&lt;/p>
&lt;h2 id="practical-advice">Practical Advice&lt;/h2>
&lt;blockquote>
&lt;p>I would say spend half your time [updating your understanding and definition of the problem], that&amp;rsquo;s what talking to customers is. That&amp;rsquo;s what sales is, sales is uncovering the problem of the person you&amp;rsquo;re talking to, you don&amp;rsquo;t know that by default. Split your time between the two. It sounds crazy to spend half your time defining the problem, but that&amp;rsquo;s actually how you should think about it. Spend 50% of your time talking to customers to update your understanding of the problem and define the right problem, and spend 50% of your time solving it, because the worst is you define a crappy problem nobody needs solved and you spend 95% of your time on the wrong problem. That would be the challenge I have to anyone who&amp;rsquo;s starting a company from an engineering background is to spend half their time defining and understanding the problem they&amp;rsquo;re solving.&lt;/p>&lt;/blockquote>
&lt;p>That quote could have started with a &lt;em>&amp;ldquo;Dear Caleb&amp;rdquo;&lt;/em>. It&amp;rsquo;s like he was speaking to me. I 100% fall prey to this trap of not solving a real problem. &lt;em>&amp;ldquo;The worst is you define a crappy problem nobody needs solved and you spend 95% of your time on the wrong problem&amp;rdquo;&lt;/em>, is the theme of my past four side projects.&lt;/p>
&lt;p>So it&amp;rsquo;s clear that I need to talk to customers. But, this leaves me with all sorts of questions. For example, how do I go about talking to customers if I don&amp;rsquo;t even know what problem I should be solving? Perhaps, the answer is to just start trying.&lt;/p></description></item><item><title>How to Organize Your Code</title><link>/blog/2020/how-to-organize-your-code/</link><pubDate>Sun, 31 May 2020 16:52:17 -0600</pubDate><guid>/blog/2020/how-to-organize-your-code/</guid><description>&lt;p>When I first started programming, I struggled a lot with how I should organize all my code. I needed an orderly way to keep track of the tutorials, code snippets, and side-projects I was rapidly accumulating. It&amp;rsquo;s definitely a tough problem.&lt;/p>
&lt;p>Thank you to Dan Fletcher for his &lt;a href="http://www.danfletcherblog.ca/2017/01/beginners-tip-organize-coding-projects/">blog post&lt;/a> on this very topic. He helped me figure this out when I was starting out. The following is a summary and rehashing of his fantastic ideas.&lt;/p>
&lt;h2 id="dont">Don&amp;rsquo;t&lt;/h2>
&lt;p>&lt;strong>Make heavy use of nesting:&lt;/strong> At first, it seems like nesting is the key to good organization. This is wrong for two main reasons. First, nesting makes long filepaths which makes navigation with the command-line inconvenient. Second, deeply nested directories impose too brittle of a hierarchy. Eventually you will have files that can live in multiple places within the hierarchy. Then the hierarchy becomes inconsistent and loses its value. Keep things simple and avoid nesting too deeply.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>nesting
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── 2020
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── feb
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │   ├── big-projects
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │   │   └── project-1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │   └── small-projects
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │   └── project-2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── mar
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ├── big-projects
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │   └── project-3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── small-projects
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── project-4
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Organize by language:&lt;/strong> For simpler projects, this is a logical conclusion. But, as projects grow in complexity they will start to make use of many languages. It is not uncommon for my projects to involve 3-4 different languages. This makes it impossible to know which directory a project should live under.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>by-language
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── go
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│   └── project-1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── javascript
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│   ├── project-2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>│   └── project-3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── python
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> └── project-4
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="do">Do&lt;/h2>
&lt;p>&lt;strong>Keep everything in one place:&lt;/strong> I have a single directory on my computer that all my programming work lives under. Get creative with the name! I call mine the &lt;code>batcave&lt;/code>. What would you call yours?&lt;/p>
&lt;p>&lt;strong>Use a flat structure:&lt;/strong> Use a single directory for every project. Each directory contains everything that the project needs. These project directories are the level at which I like to track things with Git i.e. every project directory would be its own GitHub repository.&lt;/p>
&lt;p>&lt;strong>Keep the odds and ends together:&lt;/strong> I like to group the really small stuff together. I&amp;rsquo;m talking about the code snippets and the tiny test scripts. For example, when I started learning JavaScript I made a directory called &lt;code>javascript-snippets&lt;/code>. Anytime I wanted to quickly try something out I could make a file and toss it in that directory.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>batcave
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── javascript-snippets
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── project-1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── project-2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>├── project-3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>└── project-4
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sticking to these do&amp;rsquo;s and don&amp;rsquo;ts will go a long way to keeping your code organized. Remember though, these are only guidelines — sometimes it will make sense to break them. Now go forth and code!&lt;/p></description></item><item><title>The Importance of Maintaining Dev/Prod Parity</title><link>/blog/2020/the-importance-of-maintaining-dev/prod-parity/</link><pubDate>Tue, 12 May 2020 22:17:40 -0700</pubDate><guid>/blog/2020/the-importance-of-maintaining-dev/prod-parity/</guid><description>&lt;p>One of my side projects recently died on the vine and it didn’t have to.&lt;/p>
&lt;p>The project was a web service to convert an email newsletter into a PDF for offline reading where all the links would still work. This was done by pre-rendering the content of the links into the PDF.&lt;/p>
&lt;p>Generating the PDF required a plethora of tools. I used &lt;a href="https://github.com/puppeteer/puppeteer">Puppeteer&lt;/a> to render links as HTML documents and convert them to PDFs; a mix of the Python PDF libraries &lt;a href="https://pymupdf.readthedocs.io/en/latest/">PyMuPDF&lt;/a> and &lt;a href="https://github.com/claird/PyPDF4">PyPDF4&lt;/a> to merge and link the generated PDFs; and &lt;a href="https://www.ghostscript.com/">GhostScript&lt;/a> to compress the final result.&lt;/p>
&lt;h2 id="a-world-without">A World Without&lt;/h2>
&lt;p>At the start, I hacked together a mess of Python, Node.js, and Bash scripts to prove I could get it to work. When it came time to try and deploy it in a more robust fashion, I had three obvious choices for hosting — a VPS like Digital Ocean, a PaaS like Heroku, or something serverless like AWS Lambda.&lt;/p>
&lt;p>Right out of the gate I knew Heroku wasn’t going to be an option. The process I had developed required using Puppeteer, which is really just a JavaScript wrapper around a Chromium binary. PaaS offerings are generally too sandboxed to allow you to run arbitrary binaries alongside your code. I learned this the hard way at a previous internship where I spent an entire week trying to run &lt;a href="https://developers.google.com/web/tools/lighthouse">Google Lighthouse&lt;/a> on Azure’s PaaS. Suffice to say I was not successful.&lt;/p>
&lt;p>The remaining options were to use a VPS or serverless. I knew a VPS would have been more simple to deploy to. A task queue and worker model would have let me use what I already had. It could even run on one machine. Dirt simple.&lt;/p>
&lt;p>Instead, the allure of the sub-second billing and “infinite scale” tempted me to use a serverless offering. Both these reasons shouldn’t have mattered to my zero-user side project. Regardless, I chose to use the &lt;a href="https://www.serverless.com/">Serverless Framework&lt;/a> hosted on &lt;a href="https://aws.amazon.com/lambda/">AWS Lambda&lt;/a>. I came to regret it.&lt;/p>
&lt;p>Things went okay at first. Spinning up a few Lambdas with the Serverless Framework is a breeze. But the deployment rapidly grew in complexity. First, my build step needed a bespoke docker plugin so that I could use one of my PDF libraries. Next, I had to find a way to package in binaries with my deployment so that my Lambdas could use them. Once I had the individual functions working I realized I would need to coordinate them somehow so that I could robustly handle failures. No trouble, just add a plugin to start using AWS Step Functions.&lt;/p>
&lt;p>I added all these tools/plugins/shims to be able to solve my problem, but they were starting to become a problem. My deployment times had grown to be as long as 10-15 minutes because the build step was so bloated. This alone isn’t terrible, but the straw that broke the camels back was that I couldn’t test my changes locally.&lt;/p>
&lt;p>Without the ability to test locally, development ground to a halt. Every syntax error, typo, and silly accident was locked behind 15 minutes of waiting. So much for rapid iteration. Over time, I lost interest and the project died.&lt;/p>
&lt;h2 id="devprod-parity">Dev/Prod Parity&lt;/h2>
&lt;p>If I had been able to test my project locally it wouldn’t have died. Testing changes locally would have shortened my feedback loop. 30 seconds is much more manageable than 15 minutes. In other words, what killed my project was a failure to maintain Dev/Prod parity.&lt;/p>
&lt;p>Dev/Prod parity is the idea that your local testing environment should be as close to identical to your production environment as possible. I like the description of it from the &lt;a href="https://12factor.net/dev-prod-parity">Twelve-Factor App&lt;/a>. If you aren’t already familiar with the idea of a Twelve-Factor App, you should go check it out - it’s great.&lt;/p>
&lt;p>My failed side project is an extreme example of losing environment parity; in fact, by the end, there was no parity. The only working environment was prod. But even in less extreme cases, differences in environments can start to cause big problems.&lt;/p>
&lt;h2 id="key-takeaways">Key Takeaways&lt;/h2>
&lt;p>It’s clear to me now why Dev/Prod parity is so valuable. There are two ways it will change how I work on future projects.&lt;/p>
&lt;p>First, I’m going to be more cognizant of how new features break Dev/Prod parity. When differences arise, I shouldn’t rush ahead with the feature anyway. My story is a clear example of how pushing the fix off can snowball and cause real problems. Instead, I should find a way to implement the feature without breaking parity. If this isn’t possible, it’s time to reassess how important the feature is. I want to work in a world where saying, “It runs on my machine”, also means that it runs on the production machine.&lt;/p>
&lt;p>Second, I need to start thinking about Dev/Prod parity from the very start of the project. Even before I write the first line of code. In hindsight, choosing to use AWS Lambda was a mistake for my particular situation. The tooling for doing advanced workflows with AWS Lambda is still very nascent and set me up for failure. Sure it’s easy to locally emulate a simple CRUD app with only a few functions. But local emulation is untenable once you need binaries, Step Functions, and Docker build steps.&lt;/p>
&lt;p>My side project died on the vine, but yours doesn’t have to. With the discipline to maintain Dev/Prod parity, you can avoid the issues I had.&lt;/p></description></item><item><title>Replacement for Alt-tab on the Kinesis Advantage 2</title><link>/blog/2020/replacement-for-alt-tab-on-the-kinesis-advantage-2/</link><pubDate>Sun, 19 Apr 2020 13:27:27 -0700</pubDate><guid>/blog/2020/replacement-for-alt-tab-on-the-kinesis-advantage-2/</guid><description>&lt;p>I’ve been looking for a convenient way to switch windows, ever since making the &lt;a href="https://kinesis-ergo.com/shop/advantage2/">Kinesis Advantage 2&lt;/a> my main keyboard. I used to use &lt;code>alt&lt;/code> + &lt;code>tab&lt;/code> but the unconventional shape of the Kinesis caused me to stop. It required my hand to contort too much. Recently, I found an alternative that I even prefer to &lt;code>alt&lt;/code> + &lt;code>tab&lt;/code>.&lt;/p>
&lt;p>&lt;a href="https://github.com/davatorium/rofi">Rofi&lt;/a> is a Window Switcher for Ubuntu. Its key feature is that it allows you to use fuzzy searching to select the window you want. As an example, if I had Google Chrome, Terminal and VS Code open. And I wanted to open Google Chrome. I could run Rofi, search for &lt;code>goo&lt;/code>, and hit enter. This would switch my window to the program whose name most closely matched the token &lt;code>goo&lt;/code>. In this example, it is Google Chrome like I desired.&lt;/p>
&lt;p>&lt;img src="/rofi_use.gif" alt="Rofi Usage">&lt;/p>
&lt;p>It may sound harder to have to search for the program you want, but this is exactly the reason I prefer Rofi. With &lt;code>alt&lt;/code>+&lt;code>tab&lt;/code> you always need to be cognizant of how recently you used a window. This determines where &lt;code>alt&lt;/code>+&lt;code>tab&lt;/code> orders it. With Rofi you avoid this unnecessary mental overhead and can always use the same method to pull up a window. To open Google Chrome I search &lt;code>goo&lt;/code>, to open Terminal I search &lt;code>term&lt;/code> etc. The extra keystrokes required are far and away made up for by the flexibility and consistency that Rofi provides.&lt;/p>
&lt;p>To launch Rofi you need to run &lt;code>rofi -show window&lt;/code> in a terminal. It wouldn&amp;rsquo;t be helpful if every time I wanted to switch a window I had to open a terminal and run a command. To get around this on Ubuntu you can add a custom shortcut in the &lt;code>Settings &amp;gt; Devices &amp;gt; Keyboard&lt;/code> menu. I mapped &lt;code>CTRL&lt;/code> + &lt;code>Space&lt;/code> to run the command because on the Kinesis this allows me to use both my thumbs to activate it - a very comfortable movement.&lt;/p>
&lt;p>&lt;img src="/rofi_settings.png" alt="Rofi Settings">&lt;/p>
&lt;p>I highly recommend trying this setup for window switching. Rofi has changed the way I work and I don’t think I can go back to using &lt;code>alt&lt;/code> + &lt;code>tab&lt;/code>.&lt;/p></description></item><item><title>Storing Empty Interfaces in BigCache</title><link>/blog/2020/storing-empty-interfaces-in-bigcache/</link><pubDate>Sat, 11 Apr 2020 14:12:50 -0700</pubDate><guid>/blog/2020/storing-empty-interfaces-in-bigcache/</guid><description>&lt;p>Recently at work I was tasked with adding some caching to one of our Golang services. The particular service required incoming requests to provide an API key for authentication. So on every request the service was making an extra round trip to the database to verify the API key even though it was usually the same key. Not great. Implementing the cache ended up being much harder than I thought it would be.&lt;/p>
&lt;p>After some research and exhaustive debate among the engineers, we decided that &lt;a href="https://github.com/allegro/bigcache">BigCache&lt;/a> fit our needs best.&lt;/p>
&lt;p>Here’s the catch. The signature for the set method in BigCache is &lt;code>Set(key string, entry []byte) error&lt;/code>. It expects you to store a byte slice. But, we wanted to store a struct with multiple fields that represented the API key. We might have been able to just store the bytes of the actual key this time. But this would just be putting off solving the problem. We needed a signature more like those found in other Golang cache implementations, &lt;code>Set(key, entry interface{})&lt;/code>. This would let us store anything we wanted.&lt;/p>
&lt;p>The obvious solution to this problem is serialization. If we could serialize an arbitrary struct into a byte slice, then we could store anything. To actually use the struct we stored, we could just deserialize the byte slice we get from the cache. Serializing the struct is as easy as importing any number of available encoding libraries in Golang. But now comes the headache. When we deserialize the bytes, how will the language know the shape of the struct to put the data in? Turns out &lt;code>encoding/gob&lt;/code>, the Golang specific serialization library, has this capability.&lt;/p>
&lt;p>I highly recommend you go read the &lt;a href="https://blog.golang.org/gob">blog post&lt;/a> by Rob Pike that talks all about Gob; it’s a great read. In short, Gob is a Go native way of serializing data and it also comes with the ability to serialize interface types. To enable this you need to register your type with the aptly named &lt;a href="https://golang.org/pkg/encoding/gob/#Register">register function&lt;/a> before serializing it. I ended up getting stuck here, because any code sample for &lt;code>register()&lt;/code> I found would always register a singular concrete struct or interface; I needed it to register an arbitrary &lt;code>interface{}&lt;/code> type. With a little messing around in the Go playground I discovered it can do that too.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// What most code samples did&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">type&lt;/span> foo &lt;span style="color:#069;font-weight:bold">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bar &lt;span style="color:#078;font-weight:bold">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gob.&lt;span style="color:#c0f">register&lt;/span>(foo{})
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#09f;font-style:italic">// What I needed and discovered is possible&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">var&lt;/span> &lt;span style="color:#069;font-weight:bold">type&lt;/span> &lt;span style="color:#069;font-weight:bold">interface&lt;/span>{} &lt;span style="color:#09f;font-style:italic">// Could be anything&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gob.&lt;span style="color:#c0f">register&lt;/span>(&lt;span style="color:#069;font-weight:bold">type&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="putting-it-all-together">Putting It All Together&lt;/h2>
&lt;p>With the problem of storing an arbitrary struct as bytes solved, I&amp;rsquo;ll show you how I put it all together. First we want an interface for the cache that the rest of the system can interact with. For a simple cache we don’t need much more than get and set methods.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">type&lt;/span> Cache &lt;span style="color:#069;font-weight:bold">interface&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#c0f">Set&lt;/span>(key, value &lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}) &lt;span style="color:#078;font-weight:bold">error&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#c0f">Get&lt;/span>(key &lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}) (&lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}, &lt;span style="color:#078;font-weight:bold">error&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now let’s define the BigCache implementation that will fulfill the above interface. First we need a struct that holds the cache and can have methods added to it. You could also build other things like metrics into this struct.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">type&lt;/span> bigCache &lt;span style="color:#069;font-weight:bold">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cache &lt;span style="color:#555">*&lt;/span>bigcache.BigCache
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next are the implementations of the get and set methods. Both methods assert the key is a string. From there, get and set are contrapositives of each other. One serializes a value and stores it. The other retrieves a value and deserializes it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">func&lt;/span> (c &lt;span style="color:#555">*&lt;/span>bigCache) &lt;span style="color:#c0f">Set&lt;/span>(key, value &lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}) &lt;span style="color:#078;font-weight:bold">error&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Assert the key is of string type&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> keyString, ok &lt;span style="color:#555">:=&lt;/span> key.(&lt;span style="color:#078;font-weight:bold">string&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> !ok {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> errors.&lt;span style="color:#c0f">New&lt;/span>(&lt;span style="color:#c30">&amp;#34;a cache key must be a string&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Serialize the value into bytes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> valueBytes, err &lt;span style="color:#555">:=&lt;/span> &lt;span style="color:#c0f">serialize&lt;/span>(value)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> err &lt;span style="color:#555">!=&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> c.cache.&lt;span style="color:#c0f">Set&lt;/span>(keyString, valueBytes)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">func&lt;/span> (c &lt;span style="color:#555">*&lt;/span>bigCache) &lt;span style="color:#c0f">Get&lt;/span>(key &lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}) (&lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}, &lt;span style="color:#078;font-weight:bold">error&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Assert the key is of string type&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> keyString, ok &lt;span style="color:#555">:=&lt;/span> key.(&lt;span style="color:#078;font-weight:bold">string&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> !ok {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span>, errors.&lt;span style="color:#c0f">New&lt;/span>(&lt;span style="color:#c30">&amp;#34;a cache key must be a string&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Get the value in the byte format it is stored in&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> valueBytes, err &lt;span style="color:#555">:=&lt;/span> c.cache.&lt;span style="color:#c0f">Get&lt;/span>(keyString)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> err &lt;span style="color:#555">!=&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span>, err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#09f;font-style:italic">// Deserialize the bytes of the value&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> value, err &lt;span style="color:#555">:=&lt;/span> &lt;span style="color:#c0f">deserialize&lt;/span>(valueBytes)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> err &lt;span style="color:#555">!=&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span>, err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> value, &lt;span style="color:#069;font-weight:bold">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Finally the &lt;code>encoding/gob&lt;/code> serialization logic. Beyond the use of &lt;code>register()&lt;/code> this is the fairly standard way to serialize things in Go.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-go" data-lang="go">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">serialize&lt;/span>(value &lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}) ([]&lt;span style="color:#078;font-weight:bold">byte&lt;/span>, &lt;span style="color:#078;font-weight:bold">error&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf &lt;span style="color:#555">:=&lt;/span> bytes.Buffer{}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enc &lt;span style="color:#555">:=&lt;/span> gob.&lt;span style="color:#c0f">NewEncoder&lt;/span>(&lt;span style="color:#555">&amp;amp;&lt;/span>buf)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gob.&lt;span style="color:#c0f">Register&lt;/span>(value)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> err &lt;span style="color:#555">:=&lt;/span> enc.&lt;span style="color:#c0f">Encode&lt;/span>(&lt;span style="color:#555">&amp;amp;&lt;/span>value)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> err &lt;span style="color:#555">!=&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span>, err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> buf.&lt;span style="color:#c0f">Bytes&lt;/span>(), &lt;span style="color:#069;font-weight:bold">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#069;font-weight:bold">func&lt;/span> &lt;span style="color:#c0f">deserialize&lt;/span>(valueBytes []&lt;span style="color:#078;font-weight:bold">byte&lt;/span>) (&lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}, &lt;span style="color:#078;font-weight:bold">error&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">var&lt;/span> value &lt;span style="color:#069;font-weight:bold">interface&lt;/span>{}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf &lt;span style="color:#555">:=&lt;/span> bytes.&lt;span style="color:#c0f">NewBuffer&lt;/span>(valueBytes)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dec &lt;span style="color:#555">:=&lt;/span> gob.&lt;span style="color:#c0f">NewDecoder&lt;/span>(buf)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> err &lt;span style="color:#555">:=&lt;/span> dec.&lt;span style="color:#c0f">Decode&lt;/span>(&lt;span style="color:#555">&amp;amp;&lt;/span>value)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">if&lt;/span> err &lt;span style="color:#555">!=&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> &lt;span style="color:#069;font-weight:bold">nil&lt;/span>, err
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#069;font-weight:bold">return&lt;/span> value, &lt;span style="color:#069;font-weight:bold">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And with all of that we’ve managed to store &lt;code>interface{}&lt;/code> values in BigCache. Now my team&amp;rsquo;s service is a bit more efficient. Pretty cool! If you are looking for a more comprehensive implementation, checkout my &lt;a href="https://gist.github.com/calebschoepp/0165d92de412e288aa7441e792d0aa3a">gist&lt;/a>.&lt;/p></description></item></channel></rss>